package com.ruoyi.huanjing.service.impl;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

import com.jmatio.io.MatFileReader;
import com.jmatio.types.MLArray;
import com.jmatio.types.MLDouble;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.huanjing.domain.RegionInfo;
import com.ruoyi.huanjing.entity.*;
import com.ruoyi.huanjing.service.IAtmosphericNcFileService;
import com.ruoyi.huanjing.tool.CustomColorMap;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBlockRenderer;
import org.jfree.chart.title.PaintScaleLegend;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.data.xy.DefaultXYZDataset;
import org.jfree.data.xy.XYZDataset;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.Dimension;
import ucar.nc2.dataset.NetcdfDataset;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.ruoyi.huanjing.tool.DataSmoothingUtils.bicubicInterpolation;
import static com.ruoyi.huanjing.tool.DataSmoothingUtils.gaussianFilter;

/**
 * 大气环境NC文件Service业务层处理
 *
 * @author ruoyi
 * @date 2025-07-23
 */
@Service
public class AtmosphericNcFileServiceImpl implements IAtmosphericNcFileService {

    @Autowired
    private RegionInfoServiceImpl regionInfoService;

//    @Override
//    public Map<String, Object> dimension(String filePath) {
//        // 检查文件是否为空
//        if (filePath == null || filePath.isEmpty()) {
//            Map<String, Object> result = new HashMap<>();
//            result.put("dimension", -1);
//            result.put("isGlobal", false);
//            return result;
//        }
//
//        // 从完整路径中提取文件名
//        String fileName = new File(filePath).getName();
//
//        // 根据文件名后缀判断特殊文件类型
//        if (fileName.endsWith("_SeaEMData.bin")) {
//            return createResult(9, false);
//        }
//        if (fileName.endsWith("_SeaPara.bin")) {
//            return createResult(10, false);
//        }
//        if (fileName.toLowerCase().endsWith(".asc")) {
//            return createResult(8, false);
//        }
//
//        // 检查文件后缀，若为.hdf直接返回0
//        if (filePath.toLowerCase().endsWith(".hdf")) {
//            return createResult(0, false);
//        }
//
//        // 检查是否为.nc或.NC文件，不是则返回-1
//        if (!(filePath.endsWith(".nc") || filePath.endsWith(".NC"))) {
//            return createResult(-1, false);
//        }
//
//        // 处理NetCDF文件
//        int count3D = 0; // 三维变量计数器
//        int count4D = 0; // 四维变量计数器
//        boolean isGlobal = false; // 是否为全球范围
//
//        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
//            // 读取经纬度并判断是否为全球范围
//            Variable latVar = ncFile.findVariable("latitude");
//            Variable lonVar = ncFile.findVariable("longitude");
//
//            if (latVar != null && lonVar != null) {
//                double[] latArray = extractDoubleArray(latVar.read());
//                double[] lonArray = extractDoubleArray(lonVar.read());
//
//                // 计算经纬度范围
//                double minLat = Double.MAX_VALUE;
//                double maxLat = Double.MIN_VALUE;
//                double minLon = Double.MAX_VALUE;
//                double maxLon = Double.MIN_VALUE;
//
//                for (double lat : latArray) {
//                    minLat = Math.min(minLat, lat);
//                    maxLat = Math.max(maxLat, lat);
//                }
//
//                for (double lon : lonArray) {
//                    double normalizedLon = (lon + 180) % 360 - 180;
//                    minLon = Math.min(minLon, normalizedLon);
//                    maxLon = Math.max(maxLon, normalizedLon);
//                }
//
//                // 判断是否为全球范围
//                double epsilon = 0.5; // 误差允许范围
//                boolean isFullLat = (minLat <= -90 + epsilon) && (maxLat >= 90 - epsilon);
//                boolean isFullLon = (minLon <= -180 + epsilon) && (maxLon >= 180 - epsilon);
//
//                isGlobal = isFullLat && isFullLon;
//            }
//
//            // 统计有效变量的维度
//            List<Variable> variables = ncFile.getVariables();
//            for (Variable var : variables) {
//                // 跳过字符串类型和结构体类型变量
//                if (var.getDataType().isString() || var instanceof Structure) {
//                    continue;
//                }
//
//                // 获取变量的有效维度（过滤长度为1的无效维度）
//                List<Dimension> dimensions = var.getDimensions().stream()
//                        .filter(dim -> dim.getLength() > 1)
//                        .collect(Collectors.toList());
//
//                // 判断变量类型（只处理Geo2D和2D）
//                boolean has2NScans = dimensions.stream()
//                        .anyMatch(d -> "2*nscans".equals(d.getShortName()) || "lat".equals(d.getShortName()));
//                boolean has1KMGeoDim = dimensions.stream()
//                        .anyMatch(d -> "1KM_geo_dim".equals(d.getShortName()) || "lon".equals(d.getShortName()));
//
//                boolean isGeo2D = has2NScans && has1KMGeoDim;
//                boolean is2D = dimensions.size() >= 2;
//
//                if (!isGeo2D && !is2D) {
//                    continue;
//                }
//
//                // 统计维度数量
//                int dimCount = dimensions.size();
//                if (dimCount == 3) {
//                    count3D++;
//                } else if (dimCount == 4) {
//                    count4D++;
//                }
//            }
//
//            // 确定最终维度类型
//            int finalDimension;
//            if (count3D + count4D == 1) {
//                finalDimension = (count3D == 1) ? 3 : 4;
//            } else {
//                finalDimension = (count4D > count3D) ? 4 : 3;
//            }
//
//            return createResult(finalDimension, isGlobal);
//
//        } catch (IOException e) {
//            System.err.println("读取NetCDF文件时出错: " + e.getMessage());
//            e.printStackTrace();
//            return createResult(0, false);
//        }
//    }
//
//    // 辅助方法：创建统一的结果Map
//    private Map<String, Object> createResult(int dimension, boolean isGlobal) {
//        Map<String, Object> result = new HashMap<>();
//        result.put("dimension", dimension);
//        result.put("isGlobal", isGlobal);
//        return result;
//    }

    @Override
    public Map<String, Object> dimension(String filePath) {
        // 检查文件路径是否为空
        if (filePath == null || filePath.isEmpty()) {
            return createResult(-1, false, 0, 0, 0, 0);
        }

        // 提取文件名，用于判断特殊文件类型
        String fileName = new File(filePath).getName();

        // 处理特殊后缀文件
        if (fileName.endsWith("_SeaEMData.bin")) {
            return createResult(9, false, 0, 0, 0, 0);
        }
        if (fileName.endsWith("_SeaPara.bin")) {
            return createResult(10, false, 0, 0, 0, 0);
        }
        if (fileName.toLowerCase().endsWith(".asc")) {
            return createResult(8, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".hdf")) {
            return createResult(0, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".log")) {
            return createResult(11, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".dat")) {
            return createResult(12, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".txt")) {
            return createResult(13, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".osf")||filePath.toLowerCase().endsWith(".OSF")) {
            return createResult(14, false, 0, 0, 0, 0);
        }
        if (filePath.toLowerCase().endsWith(".mat")||filePath.toLowerCase().endsWith(".MAT")) {
            return createResult(15, false, 0, 0, 0, 0);
        }

        //如果filePath里路径有："水深"
        if (filePath.contains("水深"))
        {
            return createResult(16, false, 0, 0, 0, 0);
        }
        // 仅处理 .nc 或 .NC 后缀文件
        if (!(filePath.endsWith(".nc") || filePath.endsWith(".NC"))) {

            return createResult(-1, false, 0, 0, 0, 0);
        }

        // 初始化各类变量计数器
        int count1D = 0; // 一维变量计数器
        int count2D = 0; // 二维变量计数器
        int count3D = 0; // 三维变量计数器
        int count4D = 0; // 四维变量计数器
        boolean isGlobal = false; // 是否为全球范围标记

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 读取经纬度并判断是否为全球范围
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            if (latVar != null && lonVar != null) {
                isGlobal = checkIsGlobal(latVar, lonVar);
            }

            // 遍历所有变量，统计维度
            List<Variable> variables = ncFile.getVariables();
            for (Variable var : variables) {
                // 跳过字符串类型和结构体类型变量
                if (var.getDataType().isString() || var instanceof Structure) {
                    continue;
                }

                // 过滤长度为 1 的无效维度，保留有效维度
                List<Dimension> validDimensions = var.getDimensions().stream()
                        .filter(dim -> dim.getLength() > 1)
                        .collect(Collectors.toList());

                // 根据有效维度数量判断变量维度类型
                int dimCount = validDimensions.size();
                if (dimCount == 1) {
                    count1D++;
                } else if (dimCount == 2) {
                    count2D++;
                } else if (dimCount == 3) {
                    count3D++;
                } else if (dimCount == 4) {
                    count4D++;
                }

            }

            // 确定最终维度：以最高维度为准
            int finalDimension = 0;
            if (count4D > 0) {
                finalDimension = 4; // 存在四维变量，最终维度为4
            } else if (count3D > 0) {
                finalDimension = 3; // 不存在四维但存在三维，最终维度为3
            } else if (count2D > 0) {
                finalDimension = 2; // 不存在三维及以上，存在二维，最终维度为2
            } else if (count1D > 0) {
                finalDimension = 1; // 只存在一维变量，最终维度为1
            } else {
                finalDimension = 0; // 没有有效变量
            }

            return createResult(finalDimension, isGlobal, count1D, count2D, count3D, count4D);

        } catch (IOException e) {
            System.err.println("读取 NetCDF 文件时出错: " + e.getMessage());
            e.printStackTrace();
            return createResult(0, false, 0, 0, 0, 0);
        }
    }

    /**
     * 辅助方法：判断经纬度是否覆盖全球范围
     *
     * @param latVar 纬度变量
     * @param lonVar 经度变量
     * @return 是否为全球范围
     * @throws IOException 读取变量数据异常
     */
    private boolean checkIsGlobal(Variable latVar, Variable lonVar) throws IOException {
        double[] latArray = extractDoubleArray(latVar.read());
        double[] lonArray = extractDoubleArray(lonVar.read());

        double minLat = Double.MAX_VALUE, maxLat = Double.MIN_VALUE;
        for (double lat : latArray) {
            minLat = Math.min(minLat, lat);
            maxLat = Math.max(maxLat, lat);
        }

        double minLon = Double.MAX_VALUE, maxLon = Double.MIN_VALUE;
        for (double lon : lonArray) {
            double normalizedLon = (lon + 180) % 360 - 180;
            minLon = Math.min(minLon, normalizedLon);
            maxLon = Math.max(maxLon, normalizedLon);
        }

        double epsilon = 0.5; // 误差容忍范围
        boolean isFullLat = (minLat <= -90 + epsilon) && (maxLat >= 90 - epsilon);
        boolean isFullLon = (minLon <= -180 + epsilon) && (maxLon >= 180 - epsilon);
        return isFullLat && isFullLon;
    }


    /**
     * 辅助方法：创建统一的结果 Map
     *
     * @param dimension 最终维度
     * @param isGlobal  是否为全球范围
     * @param count1D   一维变量数量
     * @param count2D   二维变量数量
     * @param count3D   三维变量数量
     * @param count4D   四维变量数量
     * @return 包含维度、是否全球范围、各维度变量数量的 Map
     */
    private Map<String, Object> createResult(int dimension, boolean isGlobal,
                                             int count1D, int count2D, int count3D, int count4D) {
        Map<String, Object> result = new HashMap<>();
        result.put("dimension", dimension);
        result.put("isGlobal", isGlobal);
        result.put("count1D", count1D);
        result.put("count2D", count2D);
        result.put("count3D", count3D);
        result.put("count4D", count4D);
        return result;
    }


    @Override
    public List<RegionInfo> readRegionList(String filePath) {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 获取经纬度变量
//            Variable latVar = ncFile.findVariable("MODIS_SWATH_Type_L1B/Geolocation_Fields/Latitude");
//            Variable lonVar = ncFile.findVariable("MODIS_SWATH_Type_L1B/Geolocation_Fields/Longitude");

            // 定义可能的经纬度变量名
            String[] latNames = {"lat", "latitude", "Latitude"};
            String[] lonNames = {"lon", "longitude", "Longitude"};

            Variable latVar = findVariableRecursively(ncFile, latNames);
            Variable lonVar = findVariableRecursively(ncFile, lonNames);

            if (latVar == null || lonVar == null) {
                throw new RuntimeException("未找到经纬度变量");
            }

            // 读取经纬度数据并计算范围
            Array latArray = latVar.read();
            Array lonArray = lonVar.read();

            // 处理纬度数组：截断至一位小数并去重
            Set<Double> uniqueLats = new HashSet<>();
            for (int i = 0; i < latArray.getSize(); i++) {
                double lat = latArray.getDouble(i);
                double truncatedLat = (int) (lat * 10) / 10.0;
                uniqueLats.add(truncatedLat);
            }
            List<Double> latList = new ArrayList<>(uniqueLats);
            Collections.sort(latList);

            // 处理经度数组：截断至一位小数并去重
            Set<Double> uniqueLons = new HashSet<>();
            for (int i = 0; i < lonArray.getSize(); i++) {
                double lon = lonArray.getDouble(i);
                double truncatedLon = (int) (lon * 10) / 10.0;
                uniqueLons.add(truncatedLon);
            }
            List<Double> lonList = new ArrayList<>(uniqueLons);
            Collections.sort(lonList);

            double minLat = Double.MAX_VALUE, maxLat = Double.MIN_VALUE;
            double minLon = Double.MAX_VALUE, maxLon = Double.MIN_VALUE;

            // 计算纬度范围
            for (double lat : latList) {
                minLat = Math.min(minLat, lat);
                maxLat = Math.max(maxLat, lat);
            }

            // 计算经度范围
            for (double lon : lonList) {
                minLon = Math.min(minLon, lon);
                maxLon = Math.max(maxLon, lon);
            }

            // 查询所有区域信息
            List<RegionInfo> regions = regionInfoService.selectRegionInfoList(new RegionInfo());

            List<RegionInfo> result = new ArrayList<>();
            for (RegionInfo region : regions) {
                String latLngRange = region.getLatLngRange();

                // 解析经纬度范围JSON
                JSONArray coords = new JSONArray(latLngRange);

                // 计算区域经纬度边界
                double regionMinLng = Double.MAX_VALUE, regionMaxLng = Double.MIN_VALUE;
                double regionMinLat = Double.MAX_VALUE, regionMaxLat = Double.MIN_VALUE;

                for (Object coord : coords) {
                    JSONObject point = (JSONObject) coord;
                    double lng = point.getDouble("lng");
                    double lat = point.getDouble("lat");

                    regionMinLng = Math.min(regionMinLng, lng);
                    regionMaxLng = Math.max(regionMaxLng, lng);
                    regionMinLat = Math.min(regionMinLat, lat);
                    regionMaxLat = Math.max(regionMaxLat, lat);
                }

                // 判断NC文件是否有经纬度点在区域内
                boolean hasPointInRegion = false;
                // 遍历所有经纬度点检查是否有落在区域内的点
                // 使用去重后的经纬度列表进行区域匹配
                for (double lon : lonList) {
                    for (double lat : latList) {
                        if (lon >= regionMinLng && lon <= regionMaxLng &&
                                lat >= regionMinLat && lat <= regionMaxLat) {
                            hasPointInRegion = true;
                            break;
                        }
                    }
                    if (hasPointInRegion) {
                        break;
                    }
                }
                if (hasPointInRegion) {
                    result.add(region);
                }
            }

            return result;

        } catch (IOException e) {
            throw new RuntimeException("读取NC文件失败: " + e.getMessage());
        } catch (JSONException e) {
            throw new RuntimeException("解析区域坐标失败: " + e.getMessage());
        }
    }

    private Variable findVariableRecursively(NetcdfFile ncFile, String[] names) {
        List<Variable> allVariables = new ArrayList<>();
        collectAllVariables(ncFile.getRootGroup(), allVariables);

        for (Variable var : allVariables) {
            for (String name : names) {
                if (var.getShortName().equalsIgnoreCase(name)) {
                    return var;
                }
            }
        }
        return null;
    }

    private void collectAllVariables(ucar.nc2.Group group, List<Variable> variables) {
        variables.addAll(group.getVariables());
        for (ucar.nc2.Group subgroup : group.getGroups()) {
            collectAllVariables(subgroup, variables);
        }
    }

    /**
     * 读取NetCDF文件中的所有变量，返回带递增ID的Map列表，包含变量名和类型
     *
     * @param filePath 文件路径
     * @return 包含id、variable和type的Map数组，id从1开始递增，仅包含Geo2D和2D类型
     */
    @Override
    @Anonymous
    public List<Map<String, String>> sunReadAllVariables(@RequestParam("filePath") String filePath) {
        List<Map<String, String>> variableList = new ArrayList<>();
        int id = 1; // 递增ID计数器

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            List<Variable> variables = ncFile.getVariables();

            for (Variable var : variables) {
                Map<String, String> variableInfo = new HashMap<>();
                String name = var.getShortName();
                variableInfo.put("id", String.valueOf(id++));
                variableInfo.put("variable", var.getFullName());
                variableInfo.put("label", getDictReplacedLabel(name));

                // 获取变量的维度信息
                List<ucar.nc2.Dimension> dimensions = var.getDimensions();
                // 过滤掉可能的无效维度，这里假设字符型变量不应有有效维度
                if (var.getDataType().isString()) {
                    dimensions = new ArrayList<>();
                }
                // 新增：若为结构体类型，将维度列表置空
                if (var instanceof ucar.nc2.Structure) {
                    dimensions = new ArrayList<>();
                }
                int dimCount = dimensions.size();
                StringBuilder dimInfo = new StringBuilder();
                for (int i = 0; i < dimensions.size(); i++) {
                    if (i > 0) {
                        dimInfo.append(", ");
                    }
                    ucar.nc2.Dimension dim = dimensions.get(i);
                    dimInfo.append(dim.getShortName());
                }

                // 判断变量类型，没有维度时 type 设为 "-"
                String varType;
                if (dimCount == 0 || dimensions.stream().anyMatch(d -> d.getLength() == 1)) {
                    varType = "-";
                } else {
                    // 仅当变量同时包含 "2*nscans" 和 "1KM_geo_dim" 维度时才判定为 Geo2D 类型
                    boolean has2NScans = dimensions.stream().anyMatch(d -> d.getShortName() != null &&
                            (d.getShortName().equals("2*nscans") || d.getShortName().equals("lat")));
                    boolean has1KMGeoDim = dimensions.stream().anyMatch(d -> d.getShortName() != null &&
                            (d.getShortName().equals("1KM_geo_dim") || d.getShortName().equals("lat")));
                    if (has2NScans && has1KMGeoDim) {
                        varType = "Geo2D";
                    } else {
                        // 假设只有两个独立有效维度时判定为2D
                        if (dimCount >= 2) {
                            varType = "2D";
                        } else {
                            varType = dimCount + "D";
                        }
                    }
                }

                // 只添加Geo2D和2D类型的变量
                if ("Geo2D".equals(varType) || "2D".equals(varType)) {
                    variableInfo.put("type", varType);

                    if (!dimensions.isEmpty()) {
                        variableInfo.put("dimensions", dimInfo.toString());
                    }
                    variableList.add(variableInfo);
                }
            }

        } catch (IOException e) {
            System.err.println("读取变量列表时出错: " + e.getMessage());
            e.printStackTrace();
            // 出错时返回空列表
        }

        return variableList;
    }

    // 封装字典替换逻辑
    private String getDictReplacedLabel(String name) {
        String dictValue = DictUtils.getDictLabel("hj_bl", name);
        return dictValue != null && !dictValue.isEmpty() ? dictValue : name;
    }

    /**
     * 读取 NetCDF 文件中的 valid_time 变量，并将其转换为可读的日期时间格式。
     * <p>
     * 该方法会打开指定路径的 NetCDF 文件，查找名为 "valid_time" 的变量，
     * 并根据其单位和日历属性将时间值解析为标准日期时间字符串。
     * 每个时间点以索引和对应的时间字符串形式返回。
     * </p>
     *
     * @param filePath NetCDF 文件的路径
     * @return 包含时间信息的列表，每个元素是一个 Map，包含 "index"（索引）和 "time"（格式化后的时间字符串）
     * @throws IOException 如果文件无法打开、缺少 valid_time 变量或时间单位格式不正确时抛出
     */
    @Override
    public Map<String, Object> sunReadValidTime(String filePath, Integer year, Integer month) throws IOException {
        Map<String, Object> result = new HashMap<>();
        Set<String> yearSet = new TreeSet<>();
        Map<String, Set<String>> monthMap = new TreeMap<>();
        Map<String, List<Map<String, Object>>> dayMap = new TreeMap<>();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 查找 valid_time 变量
            Variable validTimeVar = Arrays.stream(new String[]{
                            "valid_time",
                            "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElse(null);
            if (validTimeVar == null) {
                throw new IOException("未找到valid_time变量");
            }

            // 获取时间单位和日历属性，使用更通用的默认值
            String units = getAttributeValue(validTimeVar, "units", "seconds since 1970-01-01 00:00:00");
            String calendar = getAttributeValue(validTimeVar, "calendar", "proleptic_gregorian");

            // 解析时间单位和基准时间 - 使用通用解析方法
            TimeUnitInfo timeUnitInfo = parseTimeUnits(units);
            if (timeUnitInfo == null) {
                throw new IOException("无法解析时间单位: " + units);
            }

            // 读取时间数据
            Array timeData = validTimeVar.read();
            IndexIterator iter = timeData.getIndexIterator();

            // 遍历所有时间值，构建时间层次结构
            SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
            SimpleDateFormat monthFormat = new SimpleDateFormat("MM");
            SimpleDateFormat dayFormat = new SimpleDateFormat("dd");
            SimpleDateFormat fullFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            // 记录当前索引位置
            int index = 0;
            while (iter.hasNext()) {
                // 获取原始时间值（可能是秒、小时等单位）
                double timeValue = iter.getDoubleNext();

                // 根据单位计算毫秒数
                long milliseconds = timeUnitInfo.convertToMilliseconds(timeValue);

                // 计算实际日期
                Date actualDate = new Date(timeUnitInfo.referenceDate.getTime() + milliseconds);

                String yearStr = yearFormat.format(actualDate);
                String monthStr = monthFormat.format(actualDate);
                String dayStr = dayFormat.format(actualDate);
                String fullTime = fullFormat.format(actualDate);

                // 添加年份
                yearSet.add(yearStr);

                // 添加月份到对应年份
                monthMap.computeIfAbsent(yearStr, k -> new TreeSet<>()).add(monthStr);

                // 构建年月组合键
                String yearMonthKey = yearStr + "-" + monthStr;

                // 添加日期到对应年月，并包含数组下标
                Map<String, Object> dayInfo = new HashMap<>();
                dayInfo.put("day", dayStr);
                dayInfo.put("formattedTime", fullTime);
                dayInfo.put("index", index); // 添加文件中的数组下标
                dayMap.computeIfAbsent(yearMonthKey, k -> new ArrayList<>()).add(dayInfo);

                // 增加索引
                index++;
            }

            // 设置年份列表
            result.put("years", new ArrayList<>(yearSet));

            // 根据传入的年份参数，确定要显示的月份
            String selectedYear = year != null ? String.valueOf(year) :
                    !yearSet.isEmpty() ? yearSet.iterator().next() : null;

            if (selectedYear != null) {
                // 设置月份列表
                Set<String> months = monthMap.getOrDefault(selectedYear, Collections.emptySet());
                result.put("months", new ArrayList<>(months));

                // 根据传入的月份参数，确定要显示的日期
                String selectedMonth = month != null ? String.format("%02d", month) :
                        !months.isEmpty() ? months.iterator().next() : null;

                if (selectedMonth != null) {
                    String yearMonthKey = selectedYear + "-" + selectedMonth;
                    // 设置日期列表，包含数组下标
                    List<Map<String, Object>> days = dayMap.getOrDefault(yearMonthKey, Collections.emptyList());
                    result.put("days", days);
                }
            }

        } catch (ParseException e) {
            throw new IOException("解析时间单位失败", e);
        }

        return result;
    }

    /**
     * 解析时间单位字符串，提取时间单位和基准时间
     */
    private TimeUnitInfo parseTimeUnits(String units) throws ParseException {
        // 分割时间单位和基准时间 (支持 "since" 或 "since " 等格式)
        String[] parts = units.split("\\s+since\\s+", 2);
        if (parts.length != 2) {
            return null;
        }

        // 提取时间单位 (如: seconds, hours, days等)
        String unitPart = parts[0].trim().toLowerCase();
        String referenceTimeStr = parts[1].trim();

        // 解析时间单位的转换因子（转换为毫秒）
        long millisPerUnit;
        if (unitPart.startsWith("second")) {
            millisPerUnit = 1000; // 秒
        } else if (unitPart.startsWith("minute")) {
            millisPerUnit = 60 * 1000; // 分钟
        } else if (unitPart.startsWith("hour")) {
            millisPerUnit = 60 * 60 * 1000; // 小时
        } else if (unitPart.startsWith("day")) {
            millisPerUnit = 24 * 60 * 60 * 1000; // 天
        } else {
            throw new ParseException("不支持的时间单位: " + unitPart, 0);
        }

        // 解析基准时间，尝试多种可能的格式
        Date referenceDate = parseReferenceDate(referenceTimeStr);
        if (referenceDate == null) {
            throw new ParseException("无法解析基准时间: " + referenceTimeStr, 0);
        }

        return new TimeUnitInfo(referenceDate, millisPerUnit);
    }

    /**
     * 尝试多种格式解析基准时间
     */
    private Date parseReferenceDate(String dateStr) {
        // 可能的日期格式列表，按常见程度排序
        String[] dateFormats = {
                "yyyy-MM-dd HH:mm:ss.SSS",
                "yyyy-MM-dd HH:mm:ss",
                "yyyy-MM-dd HH:mm",
                "yyyy-MM-dd",
                "yyyy/MM/dd HH:mm:ss",
                "yyyy/MM/dd"
        };

        for (String format : dateFormats) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat(format);
                return sdf.parse(dateStr);
            } catch (ParseException e) {
                // 尝试下一种格式
                continue;
            }
        }
        return null;
    }

    // 辅助方法：获取变量属性值
    private String getAttributeValue(Variable variable, String attrName, String defaultValue) {
        Attribute attr = variable.findAttribute(attrName);
        return attr != null ? attr.getStringValue() : defaultValue;
    }

    /**
     * 读取指定 valid_time 下的所有经纬度的热力图值及边界信息
     *
     * @param filePath  NC 文件路径
     * @param timeIndex 时间索引
     * @param val       变量名称
     * @return 包含热力图数据列表（List<Atmospheric>）、边界信息（minLon, maxLon, minLat, maxLat）和数值范围（minValue, maxValue）的Map
     * @throws IOException 如果读取文件时发生错误
     */
    @Override
    public Map<String, Object> sunReadHeatmap(String filePath, int timeIndex, String val) throws IOException, InvalidRangeException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 获取变量
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);            // 支持多名称解析气压变量（pressure_level 或 level）

            // 支持多名称解析有效时间变量（valid_time 或 time）
            Variable validTimeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));
            // 计算有效时间（使用解析到的变量）
            double validTime = validTimeVar.read().getDouble(timeIndex); // 使用多名称解析后的时间变量


            // 读取经纬度
            double[] latArray = extractDoubleArray(latVar.read());
            double[] lonArray = extractDoubleArray(lonVar.read());

            // 获取网格尺寸
            int latSize = latArray.length;
            int lonSize = lonArray.length;


            // 创建并发安全的结果集合
            List<Atmospheric> tempDataList = new CopyOnWriteArrayList<>();
            AtomicReference<Double> minLat = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLat = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLon = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLon = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minVal = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxVal = new AtomicReference<>(Double.MIN_VALUE);

            // 分块读取温度数据
            int[] origin = new int[]{timeIndex, 0, 0};
            int[] shape = new int[]{1, latSize, lonSize};
            Array tempArray = tempVar.read(origin, shape);

            // 并行处理（按纬度分块）
            int chunkSize = Math.max(1, latSize / Runtime.getRuntime().availableProcessors());
            IntStream.range(0, latSize)
                    .parallel()
                    .forEach(la -> {
                        double currentLat = latArray[la];
                        // 过滤纬度值：只保留实际值除以5能整除的点
                        if (currentLat % 5 != 0) {
                            return;
                        }

                        // 更新纬度边界（原子操作）
                        updateBoundary(currentLat, minLat, maxLat);

                        Index index = tempArray.getIndex();
                        for (int lo = 0; lo < lonSize; lo++) {
                            double currentLon = lonArray[lo];
                            // 过滤经度值：只保留实际值除以5能整除的点
                            if (currentLon % 5 != 0) {
                                continue;
                            }

                            // 将经度从 [0,360) 转换为 [-180,180)
                            double normalizedLon = (currentLon + 180) % 360 - 180;

                            // 更新经度边界（原子操作）使用转换后的经度
                            updateBoundary(normalizedLon, minLon, maxLon);

                            // 读取温度值并过滤无效数据
                            double temp = tempArray.getDouble(index.set(0, la, lo));
                            // 关键修改：过滤NaN和无效值（double类型本身不会为null，主要处理NaN）
                            if (Double.isNaN(temp) || Double.isInfinite(temp)) {
                                continue; // 跳过无效数据
                            }

                            // 更新数据值的最大最小值（原子操作）
                            if (temp < minVal.get()) {
                                minVal.set(temp);
                            }
                            if (temp > maxVal.get()) {
                                maxVal.set(temp);
                            }

                            Atmospheric atmospheric = new Atmospheric();
                            atmospheric.setLon(BigDecimal.valueOf(normalizedLon));
                            atmospheric.setLat(BigDecimal.valueOf(currentLat));
                            atmospheric.setValue(BigDecimal.valueOf(temp));
                            atmospheric.setTime(BigDecimal.valueOf(validTime));
                            tempDataList.add(atmospheric);
                        }
                    });
            // 获取变量val的单位信息
            String variableUnits = getAttributeValue(tempVar, "units", "");
            // 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLon.get());
            bounds.put("maxLon", maxLon.get());
            bounds.put("minLat", minLat.get());
            bounds.put("maxLat", maxLat.get());
            result.put("bounds", bounds);

            // 添加数据值的最大值和最小值
            Map<String, Object> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal.get());
            valueRange.put("maxValue", maxVal.get());
            valueRange.put("units", variableUnits);
            result.put("valueRange", valueRange);

            return result;
        }
    }

    /**
     * 读取指定区域、指定时间下的经纬度热力图值及边界信息
     *
     * @param filePath  NC 文件路径
     * @param timeIndex 时间索引
     * @param val       变量名称
     * @param regionId  区域ID
     * @param precision 精度，5表示只保留能被5整除的经纬度，1表示只保留能被1整除的，0.25或null表示不过滤
     * @return 包含热力图数据列表、边界信息和数值范围的Map
     */
    @Override
    public Map<String, Object> processReadSunHeatmapRegion(
            String filePath,
            int timeIndex,
            String val,
            Long regionId,
            Double precision) {  // 新增精度参数

        // 获取区域信息及经纬度范围
        RegionInfo regionInfo = regionInfoService.selectRegionInfoById(regionId);
        String latLngRange = regionInfo.getLatLngRange();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 获取变量
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);

            // 支持多名称解析有效时间变量（valid_time 或 time）
            Variable validTimeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));

            // 计算有效时间
            double validTime = validTimeVar.read().getDouble(timeIndex);

            // 读取经纬度数组
            double[] latArray = extractDoubleArray(latVar.read());
            double[] lonArray = extractDoubleArray(lonVar.read());

            // 获取网格尺寸
            int latSize = latArray.length;
            int lonSize = lonArray.length;

            // 解析区域边界坐标，计算经纬度范围（min/max lat/lng）
            double minLat = Double.MAX_VALUE;
            double maxLat = Double.MIN_VALUE;
            double minLng = Double.MAX_VALUE;
            double maxLng = Double.MIN_VALUE;

            try {
                // 解析区域经纬度边界
                JSONArray jsonArray = new JSONArray(latLngRange);
                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject pointObj = jsonArray.getJSONObject(i);
                    double lat = pointObj.getDouble("lat");
                    double lng = pointObj.getDouble("lng");

                    // 更新区域经纬度边界
                    minLat = Math.min(minLat, lat);
                    maxLat = Math.max(maxLat, lat);
                    minLng = Math.min(minLng, lng);
                    maxLng = Math.max(maxLng, lng);
                }
            } catch (JSONException e) {
                throw new IllegalArgumentException("区域经纬度格式解析错误: " + latLngRange, e);
            }

            // 查找经纬度数组中对应区域边界的最近索引
            int minLatIdx = findNearestIndex(latArray, minLat);
            int maxLatIdx = findNearestIndex(latArray, maxLat);
            int minLonIdx = findNearestIndex(lonArray, minLng);
            int maxLonIdx = findNearestIndex(lonArray, maxLng);

            // 调整索引顺序（确保min在前，max在后）
            if (minLatIdx > maxLatIdx) {
                int temp = minLatIdx;
                minLatIdx = maxLatIdx;
                maxLatIdx = temp;
            }
            if (minLonIdx > maxLonIdx) {
                int temp = minLonIdx;
                minLonIdx = maxLonIdx;
                maxLonIdx = temp;
            }

            // 用final变量保存索引，供lambda使用
            final int finalMinLatIdx = minLatIdx;
            final int finalMaxLatIdx = maxLatIdx;
            final int finalMinLonIdx = minLonIdx;
            final int finalMaxLonIdx = maxLonIdx;

            // 创建并发安全的结果集合
            List<Atmospheric> tempDataList = new CopyOnWriteArrayList<>();
            AtomicReference<Double> minVal = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxVal = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLatResult = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLatResult = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLonResult = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLonResult = new AtomicReference<>(Double.MIN_VALUE);

            // 分块读取指定范围内的温度数据（三维数据: time, latitude, longitude）
            int[] origin = new int[]{timeIndex, minLatIdx, minLonIdx};
            int[] shape = new int[]{1, maxLatIdx - minLatIdx + 1, maxLonIdx - minLonIdx + 1};
            Array tempArray = tempVar.read(origin, shape);

            // 并行处理区域内的数据（按纬度分块）
            IntStream.range(0, shape[1])
                    .parallel()
                    .forEach(la -> {
                        // 计算全局纬度索引
                        int globalLa = finalMinLatIdx + la;
                        double currentLat = latArray[globalLa];

                        // 根据精度过滤纬度值
                        if (!isValidCoordinate(currentLat, precision)) {
                            return;
                        }

                        // 更新结果中的纬度边界
                        updateBoundary(currentLat, minLatResult, maxLatResult);

                        Index index = tempArray.getIndex();
                        for (int lo = 0; lo < shape[2]; lo++) {
                            // 计算全局经度索引
                            int globalLo = finalMinLonIdx + lo;
                            double currentLon = lonArray[globalLo];

                            // 根据精度过滤经度值
                            if (!isValidCoordinate(currentLon, precision)) {
                                continue;
                            }

                            // 将经度从 [0,360) 转换为 [-180,180)
                            double normalizedLon = (currentLon + 180) % 360 - 180;
                            // 更新结果中的经度边界
                            updateBoundary(normalizedLon, minLonResult, maxLonResult);

                            // 获取当前点的温度值
                            double temp = tempArray.getDouble(index.set(0, la, lo));
                            if (Double.isNaN(temp) || Double.isInfinite(temp)) {
                                continue; // 跳过无效数据
                            }

                            // 更新温度值的最大最小值
                            if (temp < minVal.get()) {
                                minVal.set(temp);
                            }
                            if (temp > maxVal.get()) {
                                maxVal.set(temp);
                            }

                            // 创建并添加数据点
                            Atmospheric atmospheric = new Atmospheric();
                            atmospheric.setLon(BigDecimal.valueOf(normalizedLon));  // 使用转换后的经度
                            atmospheric.setLat(BigDecimal.valueOf(currentLat));
                            atmospheric.setValue(BigDecimal.valueOf(temp));
                            atmospheric.setTime(BigDecimal.valueOf(validTime));
                            tempDataList.add(atmospheric);
                        }
                    });

            // 获取变量val的单位信息
            String variableUnits = getAttributeValue(tempVar, "units", "");

            // 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            // 封装边界信息
            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLonResult.get());
            bounds.put("maxLon", maxLonResult.get());
            bounds.put("minLat", minLatResult.get());
            bounds.put("maxLat", maxLatResult.get());
            result.put("bounds", bounds);

            // 封装数值范围（最大最小值）
            Map<String, Object> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal.get());
            valueRange.put("maxValue", maxVal.get());
            valueRange.put("units", variableUnits);
            result.put("valueRange", valueRange);

            return result;
        } catch (IOException | InvalidRangeException e) {
            throw new RuntimeException("读取区域内热力图数据失败", e);
        }
    }

    /**
     * 判断坐标是否符合精度要求（复用之前的方法）
     *
     * @param coordinate 经纬度值
     * @param precision  精度，5、1或0.25/null
     * @return 是否符合要求
     */
    private boolean isValidCoordinate(double coordinate, Double precision) {
        // 处理null或0.25的情况，不进行过滤
        if (precision == null || Math.abs(precision - 0.25) < 1e-9) {
            return true;
        }

        // 处理5和1的情况，检查是否能被整除（考虑浮点数精度问题）
        double remainder = coordinate % precision;
        return Math.abs(remainder) < 1e-9 || Math.abs(remainder - precision) < 1e-9;
    }

    /**
     * 读取指定lon 和 lat 下不同 time 的 value 值，输入为具体值
     *
     * @param filePath  NC 文件路径
     * @param targetLon 具体的经度值
     * @param targetLat 具体的纬度值
     * @return 包含不同 height 对应的 value 值的列表
     */
    @Override
    public Map<String, Object> sunReadLineChart(String filePath, double targetLon, double targetLat, String val, List<Integer> timeIndexList) throws IOException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取变量并校验
            Variable timeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);
            validateVariables(timeVar, latVar, lonVar, tempVar);

            // 2. 读取经纬度数据并查找索引
            double[] lonArray = extractDoubleArray(lonVar.read());
            double[] latArray = extractDoubleArray(latVar.read());
            int lonIndex = findNearestIndex(lonArray, targetLon);
            int latIndex = findNearestIndex(latArray, targetLat);

            // 3. 解析时间单位和基准时间（使用统一的解析方法）
            String units = getAttributeValue(timeVar, "units", "seconds since 1970-01-01 00:00:00");
            String calendar = getAttributeValue(timeVar, "calendar", "proleptic_gregorian");

            // 修复：使用parseTimeUnits解析完整的units字符串
            TimeUnitInfo timeUnitInfo = parseTimeUnits(units);
            if (timeUnitInfo == null) {
                throw new IOException("无法解析时间单位: " + units);
            }
            Date referenceDate = timeUnitInfo.referenceDate;
            long millisPerUnit = timeUnitInfo.millisPerUnit;

            // 4. 验证时间索引有效性
            int timeSize = timeVar.getShape()[0];
            for (int idx : timeIndexList) {
                if (idx < 0 || idx >= timeSize) {
                    throw new IndexOutOfBoundsException("时间索引 " + idx + " 超出范围（有效范围：0~" + (timeSize - 1) + "）");
                }
            }

            // 5. 确定温度变量的维度顺序
            int[] tempShape = tempVar.getShape();
            int tempDimCount = tempShape.length;
            int timeDimIndex = -1, latDimIndex = -1, lonDimIndex = -1;
            List<Dimension> tempDims = tempVar.getDimensions();
            for (int i = 0; i < tempDims.size(); i++) {
                Dimension dim = tempDims.get(i);
                if (dim.equals(timeVar.getDimension(0))) {
                    timeDimIndex = i;
                } else if (dim.equals(latVar.getDimension(0))) {
                    latDimIndex = i;
                } else if (dim.equals(lonVar.getDimension(0))) {
                    lonDimIndex = i;
                }
            }
            if (timeDimIndex == -1 || latDimIndex == -1 || lonDimIndex == -1) {
                throw new IllegalArgumentException("变量 " + val + " 的维度与时间/经纬度不匹配");
            }
            if (tempShape[timeDimIndex] != timeSize) {
                throw new IllegalArgumentException("变量 " + val + " 与时间维度长度不匹配");
            }

            // 6. 处理指定的时间索引数据
            List<String> formattedTimes = new ArrayList<>(timeIndexList.size());
            List<Double> values = new ArrayList<>(timeIndexList.size());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            for (int idx : timeIndexList) {
                // 6.1 读取指定索引的时间值
                Array timeArray = timeVar.read(new int[]{idx}, new int[]{1});
                double timeValue = timeArray.getDouble(0); // 原始时间值（根据单位不同可能是秒、小时等）

                // 6.2 计算实际时间并格式化（使用统一的单位转换）
                long totalMillis = referenceDate.getTime() + (long) (timeValue * millisPerUnit);
                Date actualDate = new Date(totalMillis);
                String formattedTime = sdf.format(actualDate);
                formattedTimes.add(formattedTime);

                // 6.3 读取指定索引的温度值
                int[] origin = new int[tempDimCount];
                Arrays.fill(origin, 0);
                origin[timeDimIndex] = idx;
                origin[latDimIndex] = latIndex;
                origin[lonDimIndex] = lonIndex;
                Array tempArray = tempVar.read(origin, new int[]{1, 1, 1});
                double tempVal = tempArray.getDouble(0);

                if (!Double.isNaN(tempVal)) {
                    // 对数值进行小数位数格式化处理
                    double formattedValue = formatDecimalValue(tempVal);
                    values.add(formattedValue);
                } else {
                    values.add(null);
                }
            }

            // 7. 封装结果
            Map<String, Object> result = new HashMap<>(3);
            result.put("times", formattedTimes);
            result.put("values", values);
            result.put("units", getAttributeValue(tempVar, "units", ""));
            result.put("parentDirChinese", extractParentDirChinese(filePath));
            return result;
        } catch (InvalidRangeException | ParseException e) {
            throw new IOException("读取数据失败", e);
        }
    }

    /**
     * 根据数值大小动态保留不同的小数位数：
     * - 大于等于 1：保留 2 位小数
     * - 大于等于 0.1 且小于 1：保留 3 位小数
     * - 大于等于 0.01 且小于 0.1：保留 4 位小数
     * - 大于等于 0.001 且小于 0.01：保留 5 位小数
     * - 以此类推...
     *
     * @param value 要格式化的数值
     * @return 格式化后的数值
     */
    private double formatDecimalValue(double value) {
        // 处理负数
        boolean isNegative = value < 0;
        double absValue = Math.abs(value);

        int decimalPlaces;
        if (absValue >= 1) {
            decimalPlaces = 2;
        } else {
            decimalPlaces = 3;
            double threshold = 0.1;
            // 每缩小10倍，多保留一位小数
            while (absValue < threshold && threshold > 1e-15) {
                decimalPlaces++;
                threshold /= 10;
            }
        }

        // 使用BigDecimal进行精确的四舍五入
        BigDecimal bd = new BigDecimal(absValue);
        bd = bd.setScale(decimalPlaces, RoundingMode.HALF_UP);

        // 恢复负数符号
        double result = bd.doubleValue();
        return isNegative ? -result : result;
    }

    @Override
    public List<Map<Object, Object>> processReadAllPressureLevel(String filePath) throws IOException {
        List<Map<Object, Object>> resultList = new ArrayList<>();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 查找pressure_level变量
            Variable pressureVar = Arrays.stream(new String[]{
                            "pressure_level",
                            "level"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElse(null);
            if (pressureVar == null) {
                throw new IOException("NC文件中未找到pressure_level变量");
            }

            // 2. 读取变量的单位属性（默认空字符串）
            String units = getAttributeValue(pressureVar, "units", "");

            // 3. 读取压力值数据
            Array pressureArray = pressureVar.read();
            int pressureSize = pressureArray.getShape()[0]; // 获取压力层级数量（37）

            // 4. 遍历数据，封装每个压力层级的信息
            Index index = pressureArray.getIndex();
            for (int i = 0; i < pressureSize; i++) {
                // 获取当前索引的压力值
                double pressureValue = pressureArray.getDouble(index.set(i));

                // 封装为Map（包含下标、压力值、单位）
                Map<Object, Object> pressureInfo = new HashMap<>();
                pressureInfo.put("index", i);          // 数组下标
                pressureInfo.put("value", pressureValue); // 压力值（hPa）
                pressureInfo.put("units", units);      // 单位（如"hPa"）

                resultList.add(pressureInfo);
            }
        }

        return resultList;
    }

    /**
     * 从 NetCDF 文件中读取指定时间、气压层和变量的热力图数据，并进行处理和封装返回。
     *
     * @param filePath      NetCDF 文件路径
     * @param timeIndex     时间维度索引
     * @param pressureIndex 气压维度索引
     * @param val           要读取的变量名（如温度、湿度等）
     * @return 包含热力图数据列表、地理边界、值范围及单位信息的映射表
     * @throws IOException           当文件读取失败时抛出
     * @throws InvalidRangeException 当访问数据范围不合法时抛出
     */
    @Override
    public Map<String, Object> profilesReadHeatmap(String filePath, int timeIndex, int pressureIndex, String val) throws IOException, InvalidRangeException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 获取变量
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);

            // 支持多名称解析气压变量（pressure_level 或 level）
            Variable pressureVar = Arrays.stream(new String[]{"pressure_level", "level"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到气压变量（pressure_level 或 level）"));

            // 支持多名称解析有效时间变量（valid_time 或 time）
            Variable validTimeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));

            // 计算有效时间和气压值
            double validTime = validTimeVar.read().getDouble(timeIndex);
            double pressureLevel = pressureVar.read().getDouble(pressureIndex);

            // 读取经纬度
            double[] latArray = extractDoubleArray(latVar.read());
            double[] lonArray = extractDoubleArray(lonVar.read());

            // 获取网格尺寸
            int latSize = latArray.length;
            int lonSize = lonArray.length;

            // 创建并发安全的结果集合
            List<Atmospheric> tempDataList = new CopyOnWriteArrayList<>();
            AtomicReference<Double> minLat = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLat = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLon = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLon = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minVal = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxVal = new AtomicReference<>(Double.MIN_VALUE);

            // 分块读取数据
            int[] origin = new int[]{timeIndex, pressureIndex, 0, 0};
            int[] shape = new int[]{1, 1, latSize, lonSize};
            Array tempArray = tempVar.read(origin, shape);

            // 获取short类型转换参数（如果变量是short类型）
            boolean isShortType = tempVar.getDataType().equals(DataType.SHORT);
            double scaleFactor;
            double addOffset;
            short fillValue;
            short missingValue;

            if (isShortType) {
                scaleFactor = tempVar.findAttribute("scale_factor").getNumericValue().doubleValue();
                addOffset = tempVar.findAttribute("add_offset").getNumericValue().doubleValue();
                fillValue = tempVar.findAttribute("_FillValue").getNumericValue().shortValue();
                missingValue = tempVar.findAttribute("missing_value").getNumericValue().shortValue();
            } else {
                addOffset = 0.0;
                scaleFactor = 1.0;
                missingValue = 0;
                fillValue = 0;
            }

            // 并行处理
            int chunkSize = Math.max(1, latSize / Runtime.getRuntime().availableProcessors());
            IntStream.range(0, latSize)
                    .parallel()
                    .forEach(la -> {
                        double currentLat = latArray[la];
                        // 过滤纬度值
                        if (currentLat % 5 != 0) {
                            return;
                        }

                        // 更新纬度边界
                        updateBoundary(currentLat, minLat, maxLat);

                        Index index = tempArray.getIndex();
                        for (int lo = 0; lo < lonSize; lo++) {
                            double currentLon = lonArray[lo];
                            // 过滤经度值
                            if (currentLon % 5 != 0) {
                                continue;
                            }

                            // 转换经度
                            double normalizedLon = (currentLon + 180) % 360 - 180;
                            updateBoundary(normalizedLon, minLon, maxLon);

                            // 根据类型读取并转换值
                            double temp;
                            if (isShortType) {
                                short rawValue = tempArray.getShort(index.set(0, 0, la, lo));
                                // 处理填充值和无效数据
                                if (rawValue == fillValue || rawValue == missingValue) {
                                    continue;
                                }
                                // 应用转换公式
                                temp = rawValue * scaleFactor + addOffset;
                            } else {
                                // 非short类型直接读取double
                                temp = tempArray.getDouble(index.set(0, 0, la, lo));
                            }

                            // 跳过无效数据
                            if (Double.isNaN(temp) || Double.isInfinite(temp)) {
                                continue;
                            }

                            // 更新数据值边界
                            if (temp < minVal.get()) {
                                minVal.set(temp);
                            }
                            if (temp > maxVal.get()) {
                                maxVal.set(temp);
                            }

                            // 封装数据
                            Atmospheric atmospheric = new Atmospheric();
                            atmospheric.setLon(BigDecimal.valueOf(normalizedLon));
                            atmospheric.setLat(BigDecimal.valueOf(currentLat));
                            atmospheric.setValue(BigDecimal.valueOf(temp));
                            atmospheric.setTime(BigDecimal.valueOf(validTime));
                            atmospheric.setPressure(BigDecimal.valueOf(pressureLevel));
                            tempDataList.add(atmospheric);
                        }
                    });

            // 获取单位信息
            String variableUnits = getAttributeValue(tempVar, "units", "");

            // 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLon.get());
            bounds.put("maxLon", maxLon.get());
            bounds.put("minLat", minLat.get());
            bounds.put("maxLat", maxLat.get());
            result.put("bounds", bounds);

            Map<String, Object> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal.get());
            valueRange.put("maxValue", maxVal.get());
            valueRange.put("units", variableUnits);
            result.put("valueRange", valueRange);

            return result;
        }
    }

    /**
     * 根据指定区域读取 NetCDF 文件中的气象数据，并生成热力图所需的数据结构。
     *
     * <p>该方法会根据传入的文件路径、时间索引、气压索引和变量名，读取对应的气象变量数据，
     * 并结合指定区域的经纬度范围和精度要求，提取该区域内的数据点，同时计算边界和数值范围等信息。</p>
     *
     * @param filePath      NetCDF 文件路径
     * @param timeIndex     时间维度索引
     * @param pressureIndex 气压维度索引
     * @param val           要读取的气象变量名（如 temperature）
     * @param regionId      区域 ID，用于获取区域的经纬度范围
     * @param precision     精度，5表示只保留能被5整除的经纬度，1表示只保留能被1整除的，0.25或null表示不过滤
     * @return 返回包含数据列表、边界范围和数值范围的 Map 结构：
     * <ul>
     *     <li>"dataList": List&lt;Atmospheric&gt; 类型，包含区域内所有有效数据点</li>
     *     <li>"bounds": Map&lt;String, Double&gt; 类型，包含 minLon、maxLon、minLat、maxLat 四个边界值</li>
     *     <li>"valueRange": Map&lt;String, Object&gt; 类型，包含 minValue、maxValue 和 units 三个字段</li>
     * </ul>
     * @throws RuntimeException 如果文件读取失败或变量不存在
     */
    @Override
    public Map<String, Object> processReadProfilesHeatmapByRegion(String filePath, int timeIndex, int pressureIndex, String val, Long regionId, Double precision) {

        RegionInfo regionInfo = regionInfoService.selectRegionInfoById(regionId);
        String latLngRange = regionInfo.getLatLngRange();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 获取变量
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            Variable tempVar = ncFile.findVariable(val);

            // 支持多名称解析气压变量
            Variable pressureVar = Arrays.stream(new String[]{"pressure_level", "level"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到气压变量（pressure_level 或 level）"));

            // 支持多名称解析有效时间变量
            Variable validTimeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));

            // 计算有效时间和气压值
            double validTime = validTimeVar.read().getDouble(timeIndex);
            double pressureLevel = pressureVar.read().getDouble(pressureIndex);

            // 读取经纬度数组
            double[] latArray = extractDoubleArray(latVar.read());
            double[] lonArray = extractDoubleArray(lonVar.read());

            // 获取网格尺寸
            int latSize = latArray.length;
            int lonSize = lonArray.length;

            // 解析区域边界坐标并计算包围盒
            double minLat = Double.MAX_VALUE;
            double maxLat = Double.MIN_VALUE;
            double minLng = Double.MAX_VALUE;
            double maxLng = Double.MIN_VALUE;

            try {
                JSONArray jsonArray = new JSONArray(latLngRange);
                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject pointObj = jsonArray.getJSONObject(i);
                    double lat = pointObj.getDouble("lat");
                    double lng = pointObj.getDouble("lng");

                    minLat = Math.min(minLat, lat);
                    maxLat = Math.max(maxLat, lat);
                    minLng = Math.min(minLng, lng);
                    maxLng = Math.max(maxLng, lng);
                }
            } catch (JSONException e) {
                throw new IllegalArgumentException("Invalid region coordinate format", e);
            }

            // 找到包围盒在经纬度数组中的索引范围
            int minLatIdx = findNearestIndex(latArray, minLat);
            int maxLatIdx = findNearestIndex(latArray, maxLat);
            int minLonIdx = findNearestIndex(lonArray, minLng);
            int maxLonIdx = findNearestIndex(lonArray, maxLng);

            // 调整索引顺序
            if (minLatIdx > maxLatIdx) {
                int tmp = minLatIdx;
                minLatIdx = maxLatIdx;
                maxLatIdx = tmp;
            }
            if (minLonIdx > maxLonIdx) {
                int tmp = minLonIdx;
                minLonIdx = maxLonIdx;
                maxLonIdx = tmp;
            }

            // 用final变量保存调整后的索引
            final int finalMinLatIdx = minLatIdx;
            final int finalMinLonIdx = minLonIdx;

            // 创建并发安全的结果集合
            List<Atmospheric> tempDataList = new CopyOnWriteArrayList<>();
            AtomicReference<Double> minLatResult = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLatResult = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLonResult = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLonResult = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minVal = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxVal = new AtomicReference<>(Double.MIN_VALUE);

            // 获取short类型转换参数
            boolean isShortType = tempVar.getDataType().equals(DataType.SHORT);
            double scaleFactor;
            double addOffset;
            short fillValue;
            short missingValue;

            if (isShortType) {
                scaleFactor = tempVar.findAttribute("scale_factor").getNumericValue().doubleValue();
                addOffset = tempVar.findAttribute("add_offset").getNumericValue().doubleValue();
                fillValue = tempVar.findAttribute("_FillValue").getNumericValue().shortValue();
                missingValue = tempVar.findAttribute("missing_value").getNumericValue().shortValue();
            } else {
                addOffset = 0.0;
                scaleFactor = 1.0;
                missingValue = 0;
                fillValue = 0;
            }

            // 分块读取数据（只读取包围盒范围内的数据）
            int[] origin = new int[]{timeIndex, pressureIndex, minLatIdx, minLonIdx};
            int[] shape = new int[]{1, 1, maxLatIdx - minLatIdx + 1, maxLonIdx - minLonIdx + 1};
            Array tempArray = tempVar.read(origin, shape);

            // 并行处理
            int chunkSize = Math.max(1, shape[2] / Runtime.getRuntime().availableProcessors());
            IntStream.range(0, shape[2])
                    .parallel()
                    .forEach(la -> {
                        // 计算全局索引
                        int globalLa = finalMinLatIdx + la;
                        double currentLat = latArray[globalLa];

                        // 根据精度过滤纬度值
                        if (!isValidCoordinate(currentLat, precision)) {
                            return;
                        }

                        // 更新纬度边界
                        updateBoundary(currentLat, minLatResult, maxLatResult);

                        Index index = tempArray.getIndex();
                        for (int lo = 0; lo < shape[3]; lo++) {
                            // 计算全局索引
                            int globalLo = finalMinLonIdx + lo;
                            double currentLon = lonArray[globalLo];

                            // 根据精度过滤经度值
                            if (!isValidCoordinate(currentLon, precision)) {
                                continue;
                            }

                            // 将经度从 [0,360) 转换为 [-180,180)
                            double normalizedLon = (currentLon + 180) % 360 - 180;

                            // 更新经度边界（使用转换后的经度）
                            updateBoundary(normalizedLon, minLonResult, maxLonResult);

                            // 根据类型读取并转换值
                            double temp;
                            if (isShortType) {
                                short rawValue = tempArray.getShort(index.set(0, 0, la, lo));
                                // 处理填充值
                                if (rawValue == fillValue || rawValue == missingValue) {
                                    continue;
                                }
                                // 应用转换公式
                                temp = rawValue * scaleFactor + addOffset;
                            } else {
                                temp = tempArray.getDouble(index.set(0, 0, la, lo));
                            }

                            // 跳过无效数据
                            if (Double.isNaN(temp) || Double.isInfinite(temp)) {
                                continue;
                            }

                            // 更新数值范围
                            if (temp < minVal.get()) {
                                minVal.set(temp);
                            }
                            if (temp > maxVal.get()) {
                                maxVal.set(temp);
                            }

                            // 创建并添加数据点
                            Atmospheric atmospheric = new Atmospheric();
                            atmospheric.setLon(BigDecimal.valueOf(normalizedLon));  // 使用转换后的经度
                            atmospheric.setLat(BigDecimal.valueOf(currentLat));
                            atmospheric.setValue(BigDecimal.valueOf(temp));
                            atmospheric.setTime(BigDecimal.valueOf(validTime));
                            atmospheric.setPressure(BigDecimal.valueOf(pressureLevel));
                            tempDataList.add(atmospheric);
                        }
                    });

            // 获取单位信息
            String variableUnits = getAttributeValue(tempVar, "units", "");

            // 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("dataList", tempDataList);

            Map<String, Double> bounds = new HashMap<>();
            bounds.put("minLon", minLonResult.get());
            bounds.put("maxLon", maxLonResult.get());
            bounds.put("minLat", minLatResult.get());
            bounds.put("maxLat", maxLatResult.get());
            result.put("bounds", bounds);

            Map<String, Object> valueRange = new HashMap<>();
            valueRange.put("minValue", minVal.get());
            valueRange.put("maxValue", maxVal.get());
            valueRange.put("units", variableUnits);
            result.put("valueRange", valueRange);

            return result;
        } catch (IOException | InvalidRangeException e) {
            throw new RuntimeException("Failed to read NetCDF file", e);
        }
    }

    /**
     * 从 NetCDF 文件中读取指定经纬度、时间索引和变量的垂直剖面数据，并以折线图形式返回压力层与对应变量值的映射。
     *
     * @param filePath  NetCDF 文件路径
     * @param targetLon 目标经度
     * @param targetLat 目标纬度
     * @param val       要读取的变量名（如温度、湿度等）
     * @param timeIndex 时间维度上的索引
     * @return 包含以下键值对的结果映射：
     * - "pressures": 压力层列表（Double）
     * - "values": 对应变量值列表（Double 或 null 表示无效数据）
     * - "units": 变量单位
     * - "time": 格式化后的时间字符串（yyyy-MM-dd HH:mm:ss）
     * - "pressureUnits": 压力层单位
     * @throws IOException 如果文件读取失败、变量缺失或时间单位无法解析
     */
    @Override
    public Map<String, Object> profilesReadLineChart(String filePath, double targetLon, double targetLat, String val, int timeIndex) throws IOException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取变量并校验
            Variable latVar = ncFile.findVariable("latitude");
            Variable lonVar = ncFile.findVariable("longitude");
            // 支持多名称解析气压变量（pressure_level 或 level）
            Variable pressureVar = Arrays.stream(new String[]{"pressure_level", "level"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到气压变量（pressure_level 或 level）"));

            // 支持多名称解析有效时间变量（valid_time 或 time）
            Variable timeVar = Arrays.stream(new String[]{"valid_time", "time"})
                    .map(ncFile::findVariable)
                    .filter(Objects::nonNull)
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("未找到有效时间变量（valid_time 或 time）"));
            Variable tempVar = ncFile.findVariable(val);
            validateVariables(timeVar, latVar, lonVar, pressureVar, tempVar);

            // 2. 读取经纬度和压力层数据并查找索引
            double[] lonArray = extractDoubleArray(lonVar.read());
            double[] latArray = extractDoubleArray(latVar.read());
            double[] pressureArray = extractDoubleArray(pressureVar.read());

            int lonIndex = findNearestIndex(lonArray, targetLon);
            int latIndex = findNearestIndex(latArray, targetLat);

            // 3. 解析时间单位和基准时间（使用统一的解析方法）
            String timeUnits = getAttributeValue(timeVar, "units", "seconds since 1970-01-01 00:00:00");
            String calendar = getAttributeValue(timeVar, "calendar", "proleptic_gregorian");

            // 使用统一的时间解析逻辑
            TimeUnitInfo timeUnitInfo = parseTimeUnits(timeUnits);
            if (timeUnitInfo == null) {
                throw new IOException("无法解析时间单位: " + timeUnits);
            }
            Date referenceDate = timeUnitInfo.referenceDate;
            long millisPerUnit = timeUnitInfo.millisPerUnit;

            // 4. 验证时间索引有效性
            int timeSize = timeVar.getShape()[0];
            if (timeIndex < 0 || timeIndex >= timeSize) {
                throw new IndexOutOfBoundsException("时间索引 " + timeIndex + " 超出范围（有效范围：0~" + (timeSize - 1) + "）");
            }

            // 5. 确定变量的维度顺序
            int[] tempShape = tempVar.getShape();
            int tempDimCount = tempShape.length;
            int timeDimIndex = -1, pressureDimIndex = -1, latDimIndex = -1, lonDimIndex = -1;
            List<Dimension> tempDims = tempVar.getDimensions();
            for (int i = 0; i < tempDims.size(); i++) {
                Dimension dim = tempDims.get(i);
                if (dim.equals(timeVar.getDimension(0))) {
                    timeDimIndex = i;
                } else if (dim.equals(pressureVar.getDimension(0))) {
                    pressureDimIndex = i;
                } else if (dim.equals(latVar.getDimension(0))) {
                    latDimIndex = i;
                } else if (dim.equals(lonVar.getDimension(0))) {
                    lonDimIndex = i;
                }
            }
            // 校验维度匹配
            if (timeDimIndex == -1 || pressureDimIndex == -1 || latDimIndex == -1 || lonDimIndex == -1) {
                throw new IllegalArgumentException("变量 " + val + " 的维度与时间/压力层/经纬度不匹配");
            }
            if (tempShape[timeDimIndex] != timeSize) {
                throw new IllegalArgumentException("变量 " + val + " 与时间维度长度不匹配");
            }
            if (tempShape[pressureDimIndex] != pressureArray.length) {
                throw new IllegalArgumentException("变量 " + val + " 与压力层维度长度不匹配");
            }

            // 判断是否为short类型并获取转换参数
            boolean isShortType = tempVar.getDataType().equals(DataType.SHORT);
            double scaleFactor = 1.0;
            double addOffset = 0.0;
            short fillValue = 0;
            short missingValue = 0;

            if (isShortType) {
                scaleFactor = tempVar.findAttribute("scale_factor").getNumericValue().doubleValue();
                addOffset = tempVar.findAttribute("add_offset").getNumericValue().doubleValue();
                fillValue = tempVar.findAttribute("_FillValue").getNumericValue().shortValue();
                missingValue = tempVar.findAttribute("missing_value").getNumericValue().shortValue();
            }

            // 6. 处理指定时间点的所有压力层数据
            List<Double> pressures = new ArrayList<>(pressureArray.length);
            List<Double> values = new ArrayList<>(pressureArray.length);

            // 格式化时间
            Array timeArray = timeVar.read(new int[]{timeIndex}, new int[]{1});
            double timeValue = timeArray.getDouble(0);
            // 使用统一的时间计算方式
            long actualTimeMillis = referenceDate.getTime() + (long) (timeValue * millisPerUnit);
            Date actualDate = new Date(actualTimeMillis);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String formattedTime = sdf.format(actualDate);

            for (int pressureIndex = 0; pressureIndex < pressureArray.length; pressureIndex++) {
                double pressureLevel = pressureArray[pressureIndex];
                pressures.add(pressureLevel);

                // 读取变量值
                int[] origin = new int[tempDimCount];
                Arrays.fill(origin, 0);
                origin[timeDimIndex] = timeIndex;
                origin[pressureDimIndex] = pressureIndex;
                origin[latDimIndex] = latIndex;
                origin[lonDimIndex] = lonIndex;
                Array tempArray = tempVar.read(origin, new int[]{1, 1, 1, 1});

                // 根据类型处理值（short需转换，其他直接读取）
                double tempVal;
                if (isShortType) {
                    short rawValue = tempArray.getShort(0);
                    // 处理填充值和无效数据
                    if (rawValue == fillValue || rawValue == missingValue) {
                        tempVal = Double.NaN;
                    } else {
                        // 应用转换公式
                        tempVal = rawValue * scaleFactor + addOffset;
                    }
                } else {
                    tempVal = tempArray.getDouble(0);
                }
                // 对有效数值进行格式化处理
                if (!Double.isNaN(tempVal)) {
                    values.add(formatDecimalValue(tempVal));
                } else {
                    values.add(null);
                }
            }

            // 7. 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("pressures", pressures);
            result.put("values", values);
            result.put("units", getAttributeValue(tempVar, "units", ""));
            result.put("time", formattedTime);
            result.put("pressureUnits", getAttributeValue(pressureVar, "units", "hPa"));
            result.put("parentDirChinese", extractParentDirChinese(filePath));
            return result;
        } catch (InvalidRangeException | ParseException e) {
            throw new IOException("读取数据失败", e);
        }
    }

    /**
     * 提取文件路径中上级目录的中文名称
     *
     * @param filePath 文件完整路径
     * @return 上级目录中的中文内容，无中文则返回空字符串
     */
    private String extractParentDirChinese(String filePath) {
        if (filePath == null || filePath.trim().isEmpty()) {
            return "";
        }

        // 处理不同系统的路径分隔符，获取上级目录名称
        File file = new File(filePath);
        File parentDir = file.getParentFile();
        if (parentDir == null) {
            return ""; // 无上级目录
        }
        String parentDirName = parentDir.getName();

        // 提取目录名称中的所有中文字符
        Pattern chinesePattern = Pattern.compile("[\u4e00-\u9fa5]+");
        Matcher matcher = chinesePattern.matcher(parentDirName);

        StringBuilder chineseBuilder = new StringBuilder();
        while (matcher.find()) {
            chineseBuilder.append(matcher.group());
        }

        return chineseBuilder.toString();
    }

    // 新增：根据时间单位计算实际毫秒数（处理秒、小时、天等不同单位）
    private long calculateActualTimeMillis(String units, Date referenceDate, double timeValue) {
        long referenceMillis = referenceDate.getTime();
        // 提取时间单位（如"seconds"、"hours"、"days"）
        String unit = units.trim().split(" ")[0].toLowerCase();
        // 根据单位转换为毫秒
        switch (unit) {
            case "second":
            case "seconds":
                return referenceMillis + (long) (timeValue * 1000);
            case "minute":
            case "minutes":
                return referenceMillis + (long) (timeValue * 60 * 1000);
            case "hour":
            case "hours":
                return referenceMillis + (long) (timeValue * 3600 * 1000);
            case "day":
            case "days":
                return referenceMillis + (long) (timeValue * 86400 * 1000); // 1天=86400秒
            default:
                // 未知单位默认按秒处理，并打日志警告
                return referenceMillis + (long) (timeValue * 1000);
        }
    }

    // 重载的变量校验方法
    private void validateVariables(Variable timeVar, Variable latVar, Variable lonVar, Variable pressureVar, Variable tempVar) {
        if (timeVar == null || latVar == null || lonVar == null || pressureVar == null || tempVar == null) {
            throw new IllegalArgumentException("缺失必要的变量");
        }

    }

    // 辅助方法：合并多个double数组
    private double[] mergeDoubleArrays(List<double[]> arrays) {
        if (arrays.isEmpty()) {
            return new double[0];
        }
        int totalSize = arrays.stream().mapToInt(a -> a.length).sum();
        double[] result = new double[totalSize];
        int destPos = 0;
        for (double[] array : arrays) {
            System.arraycopy(array, 0, result, destPos, array.length);
            destPos += array.length;
        }
        return result;
    }

    // 辅助方法：根据第一个数组对两个数组进行排序（保持索引对应关系）
    private void sortByFirstArray(double[] keys, double[] values) {
        if (keys.length != values.length) {
            throw new IllegalArgumentException("两个数组长度不一致");
        }

        // 创建索引数组
        Integer[] indices = new Integer[keys.length];
        for (int i = 0; i < keys.length; i++) {
            indices[i] = i;
        }

        // 按索引对应的keys值排序
        Arrays.sort(indices, Comparator.comparingDouble(i -> keys[i]));

        // 复制排序后的数组
        double[] sortedKeys = new double[keys.length];
        double[] sortedValues = new double[values.length];
        for (int i = 0; i < keys.length; i++) {
            sortedKeys[i] = keys[indices[i]];
            sortedValues[i] = values[indices[i]];
        }

        // 复制回原数组
        System.arraycopy(sortedKeys, 0, keys, 0, keys.length);
        System.arraycopy(sortedValues, 0, values, 0, values.length);
    }

    // 辅助方法：原子更新边界值
    private void updateBoundary(double value, AtomicReference<Double> minRef, AtomicReference<Double> maxRef) {
        minRef.updateAndGet(current -> Math.min(current, value));
        maxRef.updateAndGet(current -> Math.max(current, value));
    }

    // 辅助方法：将NetCDF的Array转为double[]
    private double[] extractDoubleArray(Array array) {
        int size = Math.toIntExact(array.getSize());
        double[] result = new double[size];
        IndexIterator iter = array.getIndexIterator();
        for (int i = 0; i < size; i++) {
            result[i] = iter.getDoubleNext(); // 直接读取double值
        }
        return result;
    }

    // 辅助方法：校验变量非空
    private void validateVariables(Variable... variables) {
        for (Variable var : variables) {
            if (var == null) {
                throw new IllegalArgumentException("变量不存在");
            }
        }
    }

    /**
     * 查找数组中最接近目标值的索引（自动适配升序/降序数组）
     *
     * @param array  有序数组（升序或降序）
     * @param target 目标值
     * @return 最接近目标值的索引
     */
    private int findNearestIndex(double[] array, double target) {
        int length = array.length;
        if (length == 0) {
            return 0; // 空数组返回0（需根据业务需求调整）
        }
        if (length == 1) {
            return 0; // 单元素数组直接返回0
        }

        // 判断数组是升序还是降序
        boolean isAscending = array[0] < array[length - 1];

        // 处理边界情况
        if (isAscending) {
            if (target <= array[0]) {
                return 0;
            }
            if (target >= array[length - 1]) {
                return length - 1;
            }
        } else {
            if (target >= array[0]) {
                return 0;
            }
            if (target <= array[length - 1]) {
                return length - 1;
            }
        }

        // 二分查找核心逻辑
        int left = 0;
        int right = length - 1;
        while (left < right - 1) {
            int mid = (left + right) >>> 1;
            double midValue = array[mid];

            if (midValue == target) {
                return mid; // 精确匹配
            } else if (isAscending) {
                // 升序：目标在右侧
                if (midValue < target) {
                    left = mid;
                } else {
                    right = mid;
                }
            } else {
                // 降序：目标在右侧（值更小的方向）
                if (midValue > target) {
                    left = mid;
                } else {
                    right = mid;
                }
            }
        }

        // 比较左右哪个更接近目标值
        double leftDiff = Math.abs(array[left] - target);
        double rightDiff = Math.abs(array[right] - target);
        return leftDiff <= rightDiff ? left : right;
    }

    @Override
    public BufferedImage generateHeatmapImage(String filePath, String val) {
        SmoothMethod smoothMethod = SmoothMethod.GAUSSIAN;
        Map<String, Object> dataMap = read2DHeatmap(filePath, val);
        if (!(boolean) dataMap.get("success")) {
            throw new RuntimeException("数据读取失败: " + dataMap.get("error"));
        }

        // 1. 解析基础数据
        double[] xAxisData = (double[]) ((Map<String, Object>) dataMap.get("xAxis")).get("data");
        List<LocalDateTime> yAxisData = (List<LocalDateTime>) ((Map<String, Object>) dataMap.get("yAxis")).get("data");
        double[][] rhData = (double[][]) ((Map<String, Object>) dataMap.get("data")).get("values");
        double minValue = (double) ((Map<String, Object>) dataMap.get("data")).get("minValue");
        double maxValue = (double) ((Map<String, Object>) dataMap.get("data")).get("maxValue");

        // 2. 补充解析单位信息
        String xAxisName = (String) ((Map<String, Object>) dataMap.get("xAxis")).get("name");
        String xAxisUnits = (String) ((Map<String, Object>) dataMap.get("xAxis")).get("units");
        String yAxisName = (String) ((Map<String, Object>) dataMap.get("yAxis")).get("name");
        String yAxisUnits = (String) ((Map<String, Object>) dataMap.get("yAxis")).get("units");
        String dataName = (String) ((Map<String, Object>) dataMap.get("data")).get("name");
        String dataUnits = (String) ((Map<String, Object>) dataMap.get("data")).get("units");

        // 3. 完善标签
        String fullXAxisName = xAxisName + (xAxisUnits != null && !xAxisUnits.isEmpty() ? " (" + xAxisUnits + ")" : "");
        String fullYAxisName = yAxisName + (yAxisUnits != null && !yAxisUnits.isEmpty() ? " (" + yAxisUnits + ")" : "");
        String fullTitle = dataName + (dataUnits != null && !dataUnits.isEmpty() ? " (" + dataUnits + ")" : "") + " 热力图";

        // 4. 时间戳转换
        List<Long> yAxisTimestamps = yAxisData.stream()
                .map(dt -> dt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
                .toList();

        // 5. 数据平滑处理
        double[][] smoothData = rhData;
        if (smoothMethod == SmoothMethod.GAUSSIAN) {
            // 高斯滤波，sigma参数可根据需求调整
            smoothData = gaussianFilter(rhData, 1, 2);
        } else if (smoothMethod == SmoothMethod.INTERPOLATION) {
            // 插值平滑，这里示例将分辨率翻倍，可按需调整
            Object[] interpolationResult = bicubicInterpolation(rhData, xAxisData, getHeightAxisFromRhData(rhData),
                    rhData.length * 2, rhData[0].length * 2);
            smoothData = (double[][]) interpolationResult[0];
            xAxisData = (double[]) interpolationResult[1];
            // 假设y轴时间数据也需要同步处理，这里简单示例，实际根据数据结构调整
            yAxisTimestamps = adjustYAxisTimestamps(yAxisTimestamps, (int) interpolationResult[2]);
        }

        // 6. 构建数据集
        XYZDataset dataset = createXYZDataset(xAxisData, yAxisTimestamps, smoothData);

        // 7. 自定义颜色映射
        List<Color> colorStops = new ArrayList<>();
        colorStops.add(new Color(0, 0, 139));    // 深蓝
        colorStops.add(new Color(0, 0, 255));    // 蓝色
        colorStops.add(new Color(0, 128, 255));  // 浅蓝
        colorStops.add(new Color(0, 255, 255));  // 青色
        colorStops.add(new Color(0, 255, 128));  // 浅绿
        colorStops.add(new Color(0, 255, 0));    // 绿色
        colorStops.add(new Color(128, 255, 0));  // 黄绿
        colorStops.add(new Color(255, 255, 0));  // 黄色
        colorStops.add(new Color(255, 165, 0));  // 橙色
        colorStops.add(new Color(255, 0, 0));     // 红色
        CustomColorMap colorMap = new CustomColorMap(minValue, maxValue, colorStops);

        // 8. 创建并配置 JFreeChart 热力图
        JFreeChart chart = createHeatmapChart(dataset, xAxisData, yAxisData,
                fullXAxisName, fullYAxisName, fullTitle,
                minValue, maxValue, dataUnits, colorMap);

        // 9. 生成并返回图片
        int width = 1200;
        int height = 800;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        chart.draw(g2, new Rectangle(width, height));
        g2.dispose();

        return image;
    }

    // 辅助方法：从 rhData 中提取高度轴数据
    private double[] getHeightAxisFromRhData(double[][] rhData) {
        if (rhData.length == 0) return new double[0];
        double[] heightAxis = new double[rhData[0].length];
        for (int i = 0; i < heightAxis.length; i++) {
            heightAxis[i] = i; // 简单示例，实际应从数据中正确提取
        }
        return heightAxis;
    }

    // 辅助方法：调整 y 轴时间戳（插值后同步调整）
    private List<Long> adjustYAxisTimestamps(List<Long> originalTimestamps, int newLength) {
        List<Long> newTimestamps = new ArrayList<>();
        long minTime = originalTimestamps.get(0);
        long maxTime = originalTimestamps.get(originalTimestamps.size() - 1);
        for (int i = 0; i < newLength; i++) {
            newTimestamps.add(minTime + (maxTime - minTime) * i / (newLength - 1));
        }
        return newTimestamps;
    }

    // ---------------------- 辅助方法：构建 XYZDataset ----------------------
    private XYZDataset createXYZDataset(double[] xAxisData, List<Long> yAxisTimestamps, double[][] rhData) {
        DefaultXYZDataset dataset = new DefaultXYZDataset();
        int xLen = rhData.length;
        int yLen = rhData[0].length;

        if (xLen == 0 || yLen == 0) {
            throw new IllegalArgumentException("数据维度异常，无法构建数据集");
        }

        double[] x = new double[xLen * yLen];
        double[] y = new double[xLen * yLen];
        double[] z = new double[xLen * yLen];

        for (int xIndex = 0; xIndex < xLen; xIndex++) {
            if (rhData[xIndex].length != yLen) {
                throw new IllegalArgumentException("数据行长度不一致，索引：" + xIndex);
            }
            for (int yIndex = 0; yIndex < yLen; yIndex++) {
                int index = xIndex * yLen + yIndex;
                x[index] = xAxisData[xIndex];
                y[index] = yAxisTimestamps.get(yIndex);
                z[index] = rhData[xIndex][yIndex];
            }
        }

        dataset.addSeries("HeatmapData", new double[][]{x, y, z});
        return dataset;
    }

    // ---------------------- 辅助方法：创建 JFreeChart 热力图 ----------------------
    private JFreeChart createHeatmapChart(XYZDataset dataset, double[] xAxisData, List<LocalDateTime> yAxisData,
                                          String xAxisName, String yAxisName, String chartTitle,
                                          double minValue, double maxValue, String dataUnits,
                                          CustomColorMap colorMap) {
        JFreeChart chart = ChartFactory.createScatterPlot(
                chartTitle,
                xAxisName,
                yAxisName,
                dataset,
                PlotOrientation.VERTICAL,
                true, true, false
        );
        chart.getTitle().setFont(new Font("SimHei", Font.BOLD, 20));

        XYPlot plot = (XYPlot) chart.getPlot();
        plot.setBackgroundPaint(Color.WHITE);
        plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
        plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
        plot.setInsets(new RectangleInsets(20, 20, 20, 80));

        // 配置 X 轴
        ValueAxis xAxis = plot.getDomainAxis();
        xAxis.setLabelFont(new Font("SimHei", Font.BOLD, 16));
        xAxis.setTickLabelFont(new Font("SimHei", Font.PLAIN, 12));
        if (xAxis instanceof NumberAxis) {
            NumberAxis numberXAxis = (NumberAxis) xAxis;
            numberXAxis.setRange(xAxisData[0], xAxisData[xAxisData.length - 1]);
            numberXAxis.setAutoTickUnitSelection(false);
            double range = xAxisData[xAxisData.length - 1] - xAxisData[0];
            numberXAxis.setTickUnit(new NumberTickUnit(Math.max(1, range / 10)));
        }

        // 配置 Y 轴
        DateAxis yAxis = new DateAxis(yAxisName);
        yAxis.setLabelFont(new Font("SimHei", Font.BOLD, 16));
        yAxis.setTickLabelFont(new Font("SimHei", Font.PLAIN, 12));
        yAxis.setDateFormatOverride(new SimpleDateFormat("yyyy-MM-dd HH:mm"));
        yAxis.setTickUnit(new DateTickUnit(DateTickUnitType.HOUR, 6));
        plot.setRangeAxis(yAxis);

        // 配置渲染器和颜色映射
        XYBlockRenderer renderer = new XYBlockRenderer();
        int blockSize = Math.max(2, 20 - (int) (Math.log(xAxisData.length) * 2));
        renderer.setBlockWidth(blockSize);
        renderer.setBlockHeight(blockSize);
        renderer.setPaintScale(colorMap);

        // 配置颜色刻度条
        NumberAxis colorAxis = new NumberAxis(dataUnits != null && !dataUnits.isEmpty() ? dataUnits : "数值");
        colorAxis.setLabelFont(new Font("SimHei", Font.PLAIN, 12));
        colorAxis.setTickLabelFont(new Font("SimHei", Font.PLAIN, 12));

        PaintScaleLegend colorLegend = new PaintScaleLegend(renderer.getPaintScale(), colorAxis);
        colorLegend.setStripWidth(20);
        colorLegend.setPosition(RectangleEdge.RIGHT);

        chart.addSubtitle(colorLegend);
        plot.setRenderer(renderer);

        return chart;
    }

    /**
     * 获取指定 NetCDF 文件中的二维参数信息列表。
     * <p>
     * 该方法会解析 NetCDF 文件中的所有变量，并根据维度数量筛选出符合条件的参数：
     * - 如果文件中存在二维或更高维变量，则只返回这些变量；
     * - 如果文件中只有低于二维的变量（如一维），则排除名为 "time" 的变量后返回其余一维变量。
     * </p>
     *
     * @param filePath NetCDF 文件的路径
     * @return 包含参数信息的列表，每个元素是一个包含参数属性的 Map，包括：
     *         "id"（编号）、"variable"（变量全名）、"label"（替换后的标签名）、
     *         "dimensions"（维度信息，可选）、"type"（维度类型，如 "2D"）
     */
    @Override
    public List<Map<String, String>> get2DParameters(String filePath) {
        List<Map<String, String>> parameterList = new ArrayList<>();
        int id = 1; // 递增ID计数器
        boolean has2DParameters = false; // 标记是否存在二维参数

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            List<Variable> variables = ncFile.getVariables();

            // 先检查是否存在二维及以上维度的变量
            for (Variable var : variables) {
                List<ucar.nc2.Dimension> dimensions = getEffectiveDimensions(var);
                if (dimensions.size() >= 2) {
                    has2DParameters = true;
                    break;
                }
            }

            // 根据是否存在二维参数来决定筛选规则并提取变量信息
            for (Variable var : variables) {
                Map<String, String> paramInfo = new HashMap<>();
                String varName = var.getShortName();
                List<ucar.nc2.Dimension> dimensions = getEffectiveDimensions(var);
                int dimCount = dimensions.size();

                // 构建维度名称字符串，用于展示
                StringBuilder dimInfo = new StringBuilder();
                for (int i = 0; i < dimensions.size(); i++) {
                    if (i > 0) {
                        dimInfo.append(", ");
                    }
                    dimInfo.append(dimensions.get(i).getShortName());
                }

                // 填充参数基本信息
                paramInfo.put("id", String.valueOf(id++));
                paramInfo.put("variable", var.getFullName());
                paramInfo.put("label", getDictReplacedLabel(varName));
                if (!dimensions.isEmpty()) {
                    paramInfo.put("dimensions", dimInfo.toString());
                }
                paramInfo.put("type", dimCount + "D");

                // 按照筛选规则添加参数到结果列表
                if (has2DParameters) {
                    // 存在二维参数时，仅保留二维及以上维度的变量
                    if (dimCount >= 2) {
                        parameterList.add(paramInfo);
                    }
                } else {
                    // 不存在二维参数时，保留除 time 外的一维变量
                    if (dimCount == 1 && !"time".equals(varName)) {
                        parameterList.add(paramInfo);
                    }
                }
            }

        } catch (IOException e) {
            System.err.println("读取参数列表时出错: " + e.getMessage());
            e.printStackTrace();
        }

        return parameterList;
    }


    /**
     * 获取变量的有效维度（排除字符串类型和结构体的维度）
     *
     * @param var 变量对象
     * @return 有效维度列表
     */
    private List<ucar.nc2.Dimension> getEffectiveDimensions(Variable var) {
        List<ucar.nc2.Dimension> dimensions = var.getDimensions();

        // 过滤字符串类型变量的维度
        if (var.getDataType().isString()) {
            return new ArrayList<>();
        }

        // 过滤结构体类型的维度
        if (var instanceof ucar.nc2.Structure) {
            return new ArrayList<>();
        }

        // 过滤长度为1的维度
        return dimensions.stream()
                .filter(dim -> dim.getLength() > 1)
                .collect(Collectors.toList());
    }


    /**
     * 从 NetCDF 文件中读取二维热力图数据，包括 X 轴、Y 轴和数据值，并返回结构化结果。
     *
     * @param filePath NetCDF 文件路径
     * @param val      数据变量名称
     * @return 包含读取结果的 Map，结构如下：
     *         - success: Boolean，表示是否读取成功
     *         - error: String（可选），错误信息（如果失败）
     *         - xAxis: Map，包含 X 轴的数据、名称和单位
     *         - yAxis: Map，包含 Y 轴的时间数据、名称和单位
     *         - data: Map，包含实际二维数据、名称、单位及最值
     */
    @Override
    public Map<String, Object> read2DHeatmap(String filePath, String val) {
        Map<String, Object> result = new LinkedHashMap<>();
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取核心变量与维度信息（关键：先获取数据变量的维度列表）
            Variable dataVar = findVariableSafely(ncFile, val, "数据变量");
            // 获取数据变量的所有维度（顺序与数据shape严格一致）
            List<Dimension> dataDimensions = dataVar.getDimensions();
            if (dataDimensions.size() != 2) {
                result.put("success", false);
                result.put("error", "数据变量必须为二维结构，实际维度数量：" + dataDimensions.size());
                return result;
            }

            // 2. 根据维度名获取对应的轴变量（而非固定调用getX/YAxisVariable）
            Variable timeVar = null;
            Variable otherVar = null;
            int timeDimIndex = -1; // 时间轴在数据维度中的索引（0=第一维度，1=第二维度）
            int otherDimIndex = -1; // 普通轴在数据维度中的索引

            // 遍历数据的两个维度，区分时间轴和普通轴
            for (int i = 0; i < dataDimensions.size(); i++) {
                Dimension dim = dataDimensions.get(i);
                Variable dimVar = ncFile.findVariable(dim.getShortName());
                if (dimVar == null) {
                    result.put("success", false);
                    result.put("error", "维度对应的变量不存在：" + dim.getShortName());
                    return result;
                }

                // 判断是否为时间轴（变量名是"time"）
                if ("time".equals(dimVar.getShortName())) {
                    timeVar = dimVar;
                    timeDimIndex = i;
                } else {
                    otherVar = dimVar;
                    otherDimIndex = i;
                }
            }

            // 3. 明确X轴（普通轴）和Y轴（时间轴）的对应关系
            Variable xAxisVar = otherVar;       // 普通轴固定为X轴
            Variable yAxisVar = timeVar;        // 时间轴固定为Y轴
            if (yAxisVar == null) {
                // 兼容无时间轴的场景（按原逻辑，可根据需求调整）
                result.put("success", false);
                result.put("error", "未找到名为'time'的时间轴变量");
                return result;
            }

            // 4. 读取变量数据
            Array dataArray = dataVar.read();
            Array xAxisArray = xAxisVar.read();
            Array yAxisArray = yAxisVar.read();

            // 5. 核心修复：动态校验数据维度与轴变量的匹配关系
            int[] dataShape = dataArray.getShape();
            // 校验普通轴（X轴）与对应数据维度的长度
            if (dataShape[otherDimIndex] != xAxisArray.getShape()[0]) {
                result.put("success", false);
                result.put("error", String.format(
                        "数据第%d维度与X轴变量长度不匹配（数据维度%d长度：%d，X轴：%d）",
                        otherDimIndex + 1, otherDimIndex + 1, dataShape[otherDimIndex], xAxisArray.getShape()[0]
                ));
                return result;
            }
            // 校验时间轴（Y轴）与对应数据维度的长度
            if (dataShape[timeDimIndex] != yAxisArray.getShape()[0]) {
                result.put("success", false);
                result.put("error", String.format(
                        "数据第%d维度与Y轴变量长度不匹配（数据维度%d长度：%d，Y轴：%d）",
                        timeDimIndex + 1, timeDimIndex + 1, dataShape[timeDimIndex], yAxisArray.getShape()[0]
                ));
                return result;
            }

            // 6. 解析X轴数据及元信息（普通轴，固定为数值数组）
            double[] xAxisData = extract1DData(xAxisArray);
            String xAxisName = getAttributeValue(xAxisVar, "long_name", xAxisVar.getShortName());
            String xAxisUnits = getAttributeValue(xAxisVar, "units", "");

            // 7. 解析Y轴数据（时间轴，固定调用时间解析方法）
            List<LocalDateTime> yAxisData;
            String yAxisName = getAttributeValue(yAxisVar, "long_name", yAxisVar.getShortName());
            String yAxisUnits = getAttributeValue(yAxisVar, "units", "");
            try {
                yAxisData = parseYAxisTime(yAxisVar, yAxisArray);
            } catch (ParseException e) {
                result.put("success", false);
                result.put("error", "时间解析失败：" + e.getMessage());
                return result;
            }

            // 8. 解析二维数据（若数据维度顺序与需求不一致，可在此处调整，如transpose）
            Object data;
            try {
                data = extract2DData(dataArray, dataVar);
                // （可选）若需要固定X轴对应数据列、Y轴对应数据行，可添加维度转置逻辑
                // 例如：如果时间轴是第一维度（行），普通轴是第二维度（列），无需转置；反之则需转置
                // if (timeDimIndex == 1) {
                //     data = transpose2DData(data); // 需自行实现二维数据转置方法
                // }
            } catch (UnsupportedOperationException e) {
                result.put("success", false);
                result.put("error", e.getMessage());
                return result;
            }

            // 9. 计算数据最值
            Map<String, Number> minMax = calculateMinMax(data);

            // 10. 数据变量元信息
            String dataName = getAttributeValue(dataVar, "long_name", dataVar.getShortName());
            String dataUnits = getAttributeValue(dataVar, "units", "");

            // 11. 封装成功结果
            result.put("success", true);

            // 封装X轴信息
            Map<String, Object> xAxisMap = new HashMap<>();
            xAxisMap.put("data", xAxisData);
            xAxisMap.put("name", xAxisName);
            xAxisMap.put("units", xAxisUnits);
            result.put("xAxis", xAxisMap);

            // 封装Y轴信息
            Map<String, Object> yAxisMap = new HashMap<>();
            yAxisMap.put("data", yAxisData);
            yAxisMap.put("name", yAxisName);
            yAxisMap.put("units", yAxisUnits);
            result.put("yAxis", yAxisMap);

            // 封装数据信息
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("values", data);
            dataMap.put("name", dataName);
            dataMap.put("units", dataUnits);
            dataMap.put("minValue", minMax.get("min"));
            dataMap.put("maxValue", minMax.get("max"));
            result.put("data", dataMap);

            return result;

        } catch (IOException e) {
            result.put("success", false);
            result.put("error", "文件读取失败：" + e.getMessage());
            return result;
        } catch (IllegalArgumentException e) {
            result.put("success", false);
            result.put("error", e.getMessage());
            return result;
        }
    }





    /**
     * 辅助1：时间轴数据降采样（仅time在前时使用）
     * 规则：按步长取点，确保最后一个点不丢失
     */
    private List<LocalDateTime> sampleTimeData(List<LocalDateTime> originalTimeList, int step) {
        List<LocalDateTime> sampledTimeList = new ArrayList<>();
        // 按步长遍历原始时间列表
        for (int i = 0; i < originalTimeList.size(); i += step) {
            sampledTimeList.add(originalTimeList.get(i));
        }
        // 补充最后一个点（避免步长导致尾部数据丢失）
        if (!sampledTimeList.isEmpty()) {
            LocalDateTime lastOriginal = originalTimeList.get(originalTimeList.size() - 1);
            LocalDateTime lastSampled = sampledTimeList.get(sampledTimeList.size() - 1);
            if (!lastOriginal.equals(lastSampled)) {
                sampledTimeList.add(lastOriginal);
            }
        }
        return sampledTimeList;
    }

    /**
     * 辅助2：获取降采样后的时间索引（用于截取二维数据）
     */
    private int[] getSampledTimeIndices(List<LocalDateTime> originalTimeList, List<LocalDateTime> sampledTimeList) {
        List<Integer> indices = new ArrayList<>();
        int sampledIdx = 0;
        // 遍历原始列表，匹配采样后的时间点，记录索引
        for (int i = 0; i < originalTimeList.size() && sampledIdx < sampledTimeList.size(); i++) {
            if (originalTimeList.get(i).equals(sampledTimeList.get(sampledIdx))) {
                indices.add(i);
                sampledIdx++;
            }
        }
        // 转成int数组返回
        return indices.stream().mapToInt(Integer::intValue).toArray();
    }

    /**
     * 辅助3：获取二维数据的行数（适配不同数据格式，如double[][]/float[][]）
     */
    private int get2DDataRowCount(Object data) {
        if (data instanceof double[][]) {
            return ((double[][]) data).length;
        } else if (data instanceof float[][]) {
            return ((float[][]) data).length;
        } else {
            throw new IllegalArgumentException("不支持的数据格式：" + data.getClass().getName());
        }
    }

    /**
     * 辅助4：获取二维数据的列数
     */
    private int get2DDataColCount(Object data) {
        if (data instanceof double[][]) {
            return ((double[][]) data).length > 0 ? ((double[][]) data)[0].length : 0;
        } else if (data instanceof float[][]) {
            return ((float[][]) data).length > 0 ? ((float[][]) data)[0].length : 0;
        } else {
            throw new IllegalArgumentException("不支持的数据格式：" + data.getClass().getName());
        }
    }

    /**
     * 安全地查找NetCDF变量
     *
     * @param ncFile NetCDF文件对象，用于查找变量
     * @param varName 变量名称
     * @param desc 变量描述信息，用于异常提示
     * @return 找到的Variable对象
     * @throws IllegalArgumentException 当找不到指定变量时抛出异常
     */
    private Variable findVariableSafely(NetcdfFile ncFile, String varName, String desc) {
        Variable var = ncFile.findVariable(varName);
        if (var == null) {
            throw new IllegalArgumentException("未找到" + desc + "：" + varName);
        }
        return var;
    }


    /**
     * 获取X轴维度对应的变量
     *
     * @param ncFile NetCDF文件对象，用于查找变量
     * @param dataVar 数据变量，用于获取其维度信息
     * @return 返回与数据变量第一个维度对应的X轴变量
     */
    private Variable getXAxisVariable(NetcdfFile ncFile, Variable dataVar) {
        // 获取数据变量的第一个维度名称作为X轴维度名称
        String xDimName = dataVar.getDimensions().get(0).getShortName();
        // 根据维度名称安全地查找对应的变量
        return findVariableSafely(ncFile, xDimName, "X轴维度变量（" + xDimName + "）");
    }


// ---------------------- 辅助方法 ----------------------

    /**
     * 获取Y轴变量
     *
     * @param ncFile NetCDF文件对象
     * @param dataVar 数据变量对象
     * @return Y轴维度对应的变量对象
     */
    private Variable getYAxisVariable(NetcdfFile ncFile, Variable dataVar) {
        // 获取数据变量的第二个维度名称（Y轴维度）
        String yDimName = dataVar.getDimensions().get(1).getShortName();
        // 根据维度名称安全查找对应的变量
        return findVariableSafely(ncFile, yDimName, "Y轴维度变量（" + yDimName + "）");
    }


    /**
     * 从多维数组中提取一维数据
     * @param array 包含数据的多维数组对象
     * @return 包含提取数据的一维double数组
     */
    private double[] extract1DData(Array array) {
        // 获取数组第一维度的长度作为一维数据的长度
        int length = array.getShape()[0];
        double[] data = new double[length];
        Index index = array.getIndex();

        // 遍历第一维度，提取每个位置的double值
        for (int i = 0; i < length; i++) {
            data[i] = array.getDouble(index.set(i));
        }
        return data;
    }


    /**
     * 解析时间变量数据，将数值型时间数据转换为LocalDateTime列表
     *
     * @param timeVar 时间变量对象，用于获取时间单位等属性信息
     * @param timeArray 时间数据数组，包含具体的时间数值
     * @return LocalDateTime列表，表示解析后的时间序列
     * @throws ParseException 当时间变量缺少units属性或时间单位格式不合法时抛出异常
     */
    private List<LocalDateTime> parseYAxisTime(Variable timeVar, Array timeArray) throws ParseException {
        List<LocalDateTime> timeList = new ArrayList<>();
        double[] timeValues = extract1DData(timeArray);

        // 1. 从变量属性动态获取时间单位
        String timeUnits = getAttributeValue(timeVar, "units", null);
        if (timeUnits == null || timeUnits.trim().isEmpty()) {
            throw new ParseException("时间变量缺少units属性，无法解析时间", 0);
        }

        // 2. 复用通用解析方法（支持任意合法时间单位格式）
        TimeUnitInfo timeUnitInfo = parseTimeUnits(timeUnits);
        if (timeUnitInfo == null) {
            throw new ParseException("时间单位格式不合法：" + timeUnits, 0);
        }

        // 3. 转换所有时间值
        long baseMillis = timeUnitInfo.referenceDate.getTime();
        for (double value : timeValues) {
            long totalMillis = baseMillis + timeUnitInfo.convertToMilliseconds(value);
            Instant instant = Instant.ofEpochMilli(totalMillis);
            timeList.add(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()));
        }
        return timeList;
    }


    /**
     * 从二维数组中提取数据并转换为相应的数据类型
     *
     * @param dataArray 包含原始数据的数组对象
     * @param dataVar 包含数据类型信息的变量对象
     * @return 返回对应数据类型的二维数组对象
     * @throws UnsupportedOperationException 当数据类型不被支持时抛出异常
     */
    private Object extract2DData(Array dataArray, Variable dataVar) {
        // 获取数组的形状信息
        int[] shape = dataArray.getShape();
        int rows = shape[0];
        int cols = shape[1];
        Index index = dataArray.getIndex();

        // 根据数据类型进行相应的数据提取和转换
        if (dataVar.getDataType().isFloatingPoint()) {
            // 处理浮点数类型数据
            double[][] data = new double[rows][cols];
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    data[i][j] = dataArray.getDouble(index.set(i, j));
                }
            }
            return data;
        } else if (dataVar.getDataType().equals(DataType.SHORT)) {
            // 处理短整型数据
            short[][] data = new short[rows][cols];
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    data[i][j] = dataArray.getShort(index.set(i, j));
                }
            }
            return data;
        } else {
            throw new UnsupportedOperationException("不支持的数据类型：" + dataVar.getDataType());
        }
    }


    /**
     * 计算二维数组数据的最小值和最大值
     *
     * @param data 输入的二维数组数据，支持double[][]或short[][]类型
     * @return 包含最小值和最大值的Map，键为"min"和"max"，值为对应的数值
     */
    private Map<String, Number> calculateMinMax(Object data) {
        Map<String, Number> minMax = new HashMap<>();

        // 处理double类型的二维数组
        if (data instanceof double[][]) {
            double[][] doubleData = (double[][]) data;
            double min = Double.MAX_VALUE;
            double max = Double.MIN_VALUE;
            for (double[] row : doubleData) {
                for (double val : row) {
                    if (!Double.isNaN(val) && !Double.isInfinite(val)) {
                        min = Math.min(min, val);
                        max = Math.max(max, val);
                    }
                }
            }
            minMax.put("min", min);
            minMax.put("max", max);
        }
        // 处理short类型的二维数组
        else if (data instanceof short[][]) {
            short[][] shortData = (short[][]) data;
            short min = Short.MAX_VALUE;
            short max = Short.MIN_VALUE;
            for (short[] row : shortData) {
                for (short val : row) {
                    min = (short) Math.min(min, val);
                    max = (short) Math.max(max, val);
                }
            }
            minMax.put("min", min);
            minMax.put("max", max);
        }
        return minMax;
    }


    /**
     * 读取 NetCDF 文件中的一维线图数据，包括时间轴和对应的数据值，并返回封装后的结果。
     *
     * @param filePath NetCDF 文件的路径
     * @param val      需要读取的数据变量名称
     * @return 返回包含读取结果的 Map，结构如下：
     *         - success: Boolean，表示读取是否成功
     *         - error（可选）: String，错误信息（仅在失败时存在）
     *         - timeAxis（可选）: Map，包含时间轴信息（仅在成功时存在）
     *             - data: List<LocalDateTime>，时间点列表
     *             - name: String，时间轴名称
     *             - units: String，时间单位
     *         - data（可选）: Map，包含数据信息（仅在成功时存在）
     *             - values: Object，实际数据值（类型取决于数据类型）
     *             - name: String，数据变量名称
     *             - units: String，数据单位
     *             - minValue: Number，数据最小值
     *             - maxValue: Number，数据最大值
     */
    @Override
    public Map<String, Object> read1DLineChart(String filePath, String val) {
        Map<String, Object> result = new LinkedHashMap<>();
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取核心变量：数据变量和时间变量
            Variable dataVar = findVariableSafely(ncFile, val, "数据变量");
            Variable timeVar = findTimeVariable(ncFile, dataVar);

            // 2. 读取变量数据
            Array dataArray = dataVar.read();
            Array timeArray = timeVar.read();

            // 3. 校验数据维度是否为一维且与时间长度一致
            int[] dataShape = dataArray.getShape();
            if (dataShape.length != 1) {
                result.put("success", false);
                result.put("error", "数据变量必须为一维结构，实际维度：" + Arrays.toString(dataShape));
                return result;
            }

            int dataLength = dataShape[0];
            int timeLength = timeArray.getShape()[0];
            if (dataLength != timeLength) {
                result.put("success", false);
                result.put("error", "数据长度与时间长度不匹配（数据：" + dataLength + "，时间：" + timeLength + "）");
                return result;
            }

            // 4. 解析时间轴数据并获取时间变量元信息
            List<LocalDateTime> timeData;
            try {
                timeData = parseTimeVariable(timeVar, timeArray);
            } catch (ParseException e) {
                result.put("success", false);
                result.put("error", "时间解析失败：" + e.getMessage());
                return result;
            }
            String timeName = getAttributeValue(timeVar, "long_name", timeVar.getShortName());
            String timeUnits = getAttributeValue(timeVar, "units", "");

            // 5. 提取一维数据值
            Object data;
            try {
                data = extract1DData(dataArray);
            } catch (UnsupportedOperationException e) {
                result.put("success", false);
                result.put("error", e.getMessage());
                return result;
            }

            // 6. 计算数据的最值
            Map<String, Number> minMax = calculate1DMinMax(data);

            // 7. 获取数据变量的元信息
            String dataName = getAttributeValue(dataVar, "long_name", dataVar.getShortName());
            String dataUnits = getAttributeValue(dataVar, "units", "");

            // 8. 封装成功结果
            result.put("success", true);

            // 封装时间轴信息
            Map<String, Object> timeAxisMap = new HashMap<>();
            timeAxisMap.put("data", timeData);
            timeAxisMap.put("name", timeName);
            timeAxisMap.put("units", timeUnits);
            result.put("timeAxis", timeAxisMap);

            // 封装数据信息
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("values", data);
            dataMap.put("name", dataName);
            dataMap.put("units", dataUnits);
            dataMap.put("minValue", minMax.get("min"));
            dataMap.put("maxValue", minMax.get("max"));
            result.put("data", dataMap);

            return result;

        } catch (IOException e) {
            result.put("success", false);
            result.put("error", "文件读取失败：" + e.getMessage());
            return result;
        } catch (IllegalArgumentException e) {
            result.put("success", false);
            result.put("error", e.getMessage());
            return result;
        }
    }


    /**
     * 辅助方法：查找时间变量
     * 通过多种策略在NetCDF文件中查找时间变量
     *
     * @param ncFile NetCDF文件对象，用于查找变量
     * @param dataVar 数据变量，用于根据维度关联查找时间变量
     * @return 找到的时间变量
     * @throws IllegalArgumentException 当未找到时间变量时抛出异常
     */
    private Variable findTimeVariable(NetcdfFile ncFile, Variable dataVar) {
        // 方法1：根据变量名查找
        Variable timeVar = ncFile.findVariable("time");
        if (timeVar != null) {
            return timeVar;
        }

        // 方法2：根据维度关联查找
        for (Dimension dim : dataVar.getDimensions()) {
            Variable candidate = ncFile.findVariable(dim.getShortName());
            if (candidate != null && isTimeVariable(candidate)) {
                return candidate;
            }
        }

        throw new IllegalArgumentException("未找到时间变量，请检查NetCDF文件结构");
    }


    /**
     * 辅助方法：判断是否为时间变量
     * 通过检查变量的units属性来判断该变量是否表示时间相关的数据
     * @param var 待检查的变量对象
     * @return 如果变量的units属性包含时间相关关键词则返回true，否则返回false
     */
    private boolean isTimeVariable(Variable var) {
        // 获取变量的units属性
        Attribute unitsAttr = var.findAttribute("units");
        if (unitsAttr == null) {
            return false;
        }
        // 将units属性值转换为小写进行匹配
        String units = unitsAttr.getStringValue().toLowerCase();
        // 判断units中是否包含时间相关的关键词
        return units.contains("time") || units.contains("since") ||
                units.contains("year") || units.contains("month") ||
                units.contains("day") || units.contains("hour");
    }


    /**
     * 辅助方法：解析时间变量
     * 将NetCDF格式的时间变量解析为LocalDateTime列表
     *
     * @param timeVar 时间变量对象，包含时间单位和日历信息
     * @param timeArray 时间数组对象，包含具体的时间数值数据
     * @return LocalDateTime列表，表示解析后的时间序列
     * @throws ParseException 当时间单位不支持或时间格式无法解析时抛出异常
     */
    private List<LocalDateTime> parseTimeVariable(Variable timeVar, Array timeArray) throws ParseException {
        String timeUnits = getAttributeValue(timeVar, "units", "");
        String calendar = getAttributeValue(timeVar, "calendar", "gregorian");

        // 解析基准时间
        String baseTimeStr = timeUnits.substring(timeUnits.indexOf("since") + 6).trim();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime baseTime = LocalDateTime.parse(baseTimeStr, formatter);

        // 处理时间数据
        List<LocalDateTime> timeList = new ArrayList<>();
        Index index = timeArray.getIndex();
        for (int i = 0; i < timeArray.getSize(); i++) {
            index.set(i);
            double timeValue = timeArray.getDouble(index);

            // 根据单位添加时间偏移
            LocalDateTime actualTime;
            if (timeUnits.contains("second")) {
                actualTime = baseTime.plusSeconds((long) timeValue);
            } else if (timeUnits.contains("minute")) {
                actualTime = baseTime.plusMinutes((long) timeValue);
            } else if (timeUnits.contains("hour")) {
                actualTime = baseTime.plusHours((long) timeValue);
            } else if (timeUnits.contains("day")) {
                actualTime = baseTime.plusDays((long) timeValue);
            } else {
                throw new ParseException("不支持的时间单位: " + timeUnits, 0);
            }

            timeList.add(actualTime);
        }
        return timeList;
    }


    /**
     * 辅助方法：计算一维数据最值
     *
     * @param data 输入的一维数组数据，支持float[]、double[]、int[]类型
     * @return 包含最小值和最大值的Map，key为"min"和"max"，value为对应的最值
     * @throws UnsupportedOperationException 当输入数据类型不支持时抛出异常
     */
    private Map<String, Number> calculate1DMinMax(Object data) {
        Map<String, Number> minMax = new HashMap<>();

        // 处理float类型数组
        if (data instanceof float[]) {
            float[] arr = (float[]) data;
            float min = Float.MAX_VALUE;
            float max = Float.MIN_VALUE;
            for (float value : arr) {
                if (value < min) min = value;
                if (value > max) max = value;
            }
            minMax.put("min", min);
            minMax.put("max", max);
        // 处理double类型数组
        } else if (data instanceof double[]) {
            double[] arr = (double[]) data;
            double min = Double.MAX_VALUE;
            double max = Double.MIN_VALUE;
            for (double value : arr) {
                if (value < min) min = value;
                if (value > max) max = value;
            }
            minMax.put("min", min);
            minMax.put("max", max);
        // 处理int类型数组
        } else if (data instanceof int[]) {
            int[] arr = (int[]) data;
            int min = Integer.MAX_VALUE;
            int max = Integer.MIN_VALUE;
            for (int value : arr) {
                if (value < min) min = value;
                if (value > max) max = value;
            }
            minMax.put("min", min);
            minMax.put("max", max);
        // 不支持的数据类型处理
        } else {
            throw new UnsupportedOperationException("不支持的数据类型计算最值: " + data.getClass().getSimpleName());
        }

        return minMax;
    }



    @Override
    public Map<String, Object> readLogFile(String filePath) {
        List<String> times = new ArrayList<>(); // 存储转换后的标准时间
        List<Double> visibilities = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), getCorrectCharset(filePath)))) {

            String line;
            boolean isFirstLine = true;

            // 1. 解析数据，提取并转换时间格式
            while ((line = br.readLine()) != null) {
                if (isFirstLine) {
                    isFirstLine = false;
                    continue;
                }
                if (line.trim().isEmpty()) {
                    continue;
                }

                AtmosphericLogData logData = parseLogLine(line);
                if (logData != null && StringUtils.isNotBlank(logData.getRecordTime())) {
                    // 关键：将 "YYYY-MM-DD-HH-mm-ss" 转换为 "YYYY-MM-DD HH:mm:ss"
                    String originalTime = logData.getRecordTime();
                    // ：用String.replace分段替换（
                    String standardTime = originalTime
                            .replaceFirst("-", " ") // 第3个"-"替换为空格（分隔日期和时间）
                            .replace("-", ":");     // 剩余"-"替换为":"（时间内部分隔）


                    times.add(standardTime);
                    visibilities.add(logData.getVisibility());
                }
            }

            // 2. 封装X轴（转换后的标准时间）
            Map<String, Object> xAxisMap = new HashMap<>();
            xAxisMap.put("data", times);
            xAxisMap.put("name", "记录时间");
            result.put("xAxis", xAxisMap);

            // 3. 封装Y轴（能见度，逻辑不变）
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("values", visibilities);
            dataMap.put("name", "能见度");
            dataMap.put("units", "Km");

            // 计算最小/最大值（过滤null值）
            Double minValue = visibilities.stream()
                    .filter(Objects::nonNull)
                    .min(Double::compare)
                    .orElse(null);
            Double maxValue = visibilities.stream()
                    .filter(Objects::nonNull)
                    .max(Double::compare)
                    .orElse(null);

            dataMap.put("minValue", minValue);
            dataMap.put("maxValue", maxValue);
            dataMap.put("validCount", visibilities.size());
            result.put("data", dataMap);

        } catch (IOException e) {
            throw new RuntimeException("文件读取失败：" + e.getMessage());
        }

        return result;
    }


//    /**
//     * 读取log文件（带分页，返回总条数+分页数据，无需新增方法）
//     * @param filePath 日志文件路径
//     * @param pageNum 页码（从1开始，0表示返回全部数据）
//     * @param pageSize 每页条数（0表示返回全部数据）
//     * @return Map<String, Object> 包含两个键值对：
//     *         key="totalCount" 表示日志总条数，
//     *         key="data" 表示当前页的数据列表
//     */
//    @Override
//    public Map<String, Object> readLogFile(String filePath, int pageNum, int pageSize) {
//        List<AtmosphericLogData> allLogData = new ArrayList<>();
//        Map<String, Object> resultMap = new HashMap<>(); // 封装总条数和分页数据
//
//        try (BufferedReader br = new BufferedReader(
//                new InputStreamReader(new FileInputStream(filePath), getCorrectCharset(filePath)))) {
//
//            String line;
//            boolean isFirstLine = true;
//
//            // 1. 解析全部数据
//            while ((line = br.readLine()) != null) {
//                if (isFirstLine) {
//                    isFirstLine = false;
//                    continue;
//                }
//                if (line.trim().isEmpty()) {
//                    continue;
//                }
//                AtmosphericLogData logData = parseLogLine(line);
//                if (logData != null) {
//                    allLogData.add(logData);
//                }
//            }
//
//            // 2. 记录总条数（关键：一次解析就拿到总条数，无需额外方法）
//            int totalCount = allLogData.size();
//            resultMap.put("totalCount", totalCount);
//
//            // 3. 分页处理
//            List<AtmosphericLogData> pagedData = allLogData; // 默认返回全部
//            if (pageNum > 0 && pageSize > 0) {
//                int startIndex = (pageNum - 1) * pageSize;
//                int endIndex = Math.min(startIndex + pageSize, totalCount);
//                if (startIndex < totalCount) {
//                    pagedData = allLogData.subList(startIndex, endIndex);
//                } else {
//                    pagedData = new ArrayList<>(); // 页码超出时返回空列表
//                }
//            }
//            resultMap.put("data", pagedData); // 分页后的数据
//
//        } catch (IOException e) {
//            throw new RuntimeException("文件读取失败：" + e.getMessage());
//        }
//
//        return resultMap; // 返回「总条数+分页数据」
//    }





    /**
     * 解析大气数据日志行，将一行文本数据转换为AtmosphericLogData对象
     *
     * @param line 包含大气数据的日志行文本，字段间以空格分隔
     * @return 解析成功返回AtmosphericLogData对象，解析失败或格式错误返回null
     */
    private AtmosphericLogData parseLogLine(String line) {
        // 按空格分割日志行字段，有效格式应包含14或16个字段
        String[] fields = line.split("\\s+");
        if (fields.length != 14 && fields.length != 16) {
            return null;
        }

        AtmosphericLogData logData = new AtmosphericLogData();
        try {
            // 解析基本大气数据字段（前14个字段）
            logData.setRecordTime(fields[0].trim());
            logData.setSurfaceRadiation(parseDouble(fields[1]));
            logData.setRadiometerTemperature(parseDouble(fields[2]));
            logData.setVisibility(parseDouble(fields[3]));
            logData.setExtinctionCoefficient(parseDouble(fields[4]));
            logData.setAverageWindSpeed(parseDouble(fields[5]));
            logData.setAverageWindDirection(parseDouble(fields[6]));
            logData.setAirTemperature(parseDouble(fields[7]));
            logData.setAirPressure(parseDouble(fields[8]));
            logData.setRelativeHumidity(parseDouble(fields[9]));
            logData.setTotalRainfall(parseDouble(fields[10]));
            logData.setRainfallIntensity(parseDouble(fields[11]));
            logData.setTotalHailAmount(parseDouble(fields[12]));
            logData.setHailIntensity(parseDouble(fields[13]));

            // 如果字段数为16，则解析经纬度信息
            if (fields.length == 16) {
                logData.setLatitude(parseDouble(fields[14]));
                logData.setLongitude(parseDouble(fields[15]));
            }
        } catch (NumberFormatException e) {
            // 数值转换异常时返回null
            return null;
        }
        return logData;
    }


    /**
     * 解析字符串为Double类型数值
     * <p>
     * 该方法会先去除字符串两端的空白字符，然后判断是否为空字符串。
     * 如果是空字符串则返回0.0，否则将字符串解析为Double数值。
     *
     * @param str 待解析的字符串，可以为null或空字符串
     * @return 解析后的Double数值，如果输入字符串为空则返回0.0
     */
    private Double parseDouble(String str) {
        String trimStr = str.trim();
        return trimStr.isEmpty() ? 0.0 : Double.parseDouble(trimStr);
    }


    /**
     * 获取文件的正确字符编码
     * 通过尝试不同的字符编码来读取文件，判断哪种编码能够正确解析文件内容
     * @param filePath 文件路径
     * @return 检测到的正确字符编码，如果都失败则返回UTF-8编码
     */
    private Charset getCorrectCharset(String filePath) {
        // 定义可能的字符编码数组，按优先级排列
        Charset[] possibleCharsets = {Charset.forName("GBK"), StandardCharsets.UTF_8, Charset.forName("GB2312")};
        // 依次尝试每种可能的字符编码
        for (Charset charset : possibleCharsets) {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), charset))) {
                String firstLine = br.readLine();
                // 如果能读取到第一行且包含"记录时间"关键字，则认为是正确的编码
                if (firstLine != null && firstLine.contains("记录时间")) {
                    return charset;
                }
            } catch (IOException e) {
                // 如果当前编码读取失败，继续尝试下一种编码
                continue;
            }
        }
        // 如果所有编码都尝试失败，返回默认的UTF-8编码
        return StandardCharsets.UTF_8;
    }


    /**
     * 读取文件中的所有时间，返回原始格式时间列表
     *
     * @param filePath 数据文件绝对路径
     * @return 时间列表（按文件中出现顺序排列，无重复）
     */
    @Override
    public List<String> readDatBySeaCurrentTime(String filePath) {
        List<String> timeList = new ArrayList<>();

        // 缓冲流读取文件（自动关闭资源，try-with-resources语法）
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty()) continue;

                // 仅处理记录类型为"2"的行（文档表2：时间信息行）
                if (line.startsWith("2")) {
                    // 按文档格式截取时间：起始位置3（下标2），长度12
                    String time = substringToString(line, 2, 12).trim();
                    // 避免重复时间添加
                    if (!time.isEmpty() && !timeList.contains(time)) {
                        timeList.add(time);
                    }
                }
            }
        } catch (IOException e) {
            // 抛出运行时异常，由上层控制器统一处理
            throw new RuntimeException("读取时间列表失败：" + e.getMessage());
        }

        return timeList;
    }


    /**
     * 根据指定时间，获取对应的数据（水深、流速、流向）
     *
     * @param filePath   数据文件绝对路径
     * @param targetTime 目标时间（需与文件中原始时间格式一致，如"201901010000"）
     * @return 映射关系：key=数据标识，value=对应数据（包含头部信息、单位信息和列表数据）
     */
    @Override
    public Map<String, Object> readDatBySeaCurrentTimeSeries(String filePath, String targetTime) {
        Map<String, Object> result = new HashMap<>(4); // 初始容量优化

        // 初始化数据列表，使用ArrayList并指定初始容量
        List<Double> yList = new ArrayList<>();
        List<Double> x1List = new ArrayList<>();
        List<Double> x2List = new ArrayList<>();

        result.put("YList", yList);
        result.put("X1List", x1List);
        result.put("X2List", x2List);

        // 添加头部信息（包含中文名称）
        Map<String, String> header = new HashMap<>(3);
        header.put("Y", "水深");
        header.put("X1", "流速");
        header.put("X2", "流向");
        result.put("header", header);

        // 添加单位信息
        Map<String, String> unit = new HashMap<>(3);
        unit.put("Y", "(m)");
        unit.put("X1", "(m/s)");
        unit.put("X2", "(°)");
        result.put("unit", unit);

        boolean timeFound = false;
        // 预编译目标时间的trimmed版本，避免重复处理
        String trimmedTargetTime = targetTime.trim();

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            // 循环读取直到找到目标时间并处理完其数据
            while ((line = br.readLine()) != null) {
                if (line.isEmpty()) continue;

                // 检查是否是时间行
                if (line.charAt(0) == '2') {
                    // 如果已经找到目标时间并处理了数据，遇到下一个时间行则退出
                    if (timeFound) {
                        break;
                    }

                    // 快速定位时间字符串位置（假设格式是"2 202001010000"）
                    int timeStart = line.indexOf(' ');
                    if (timeStart != -1 && timeStart + 1 < line.length()) {
                        String currentTime = line.substring(timeStart + 1).trim();
                        if (currentTime.equals(trimmedTargetTime)) {
                            timeFound = true;
                        }
                    }
                }
                // 处理数据行
                else if (timeFound && line.charAt(0) == '3') {
                    // 直接解析，避免创建中间对象
                    int index = 1; // 跳过第一个字符'3'
                    int end;

                    // 解析水深
                    while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
                        index++;
                    }
                    end = index;
                    while (end < line.length() && !Character.isWhitespace(line.charAt(end))) {
                        end++;
                    }
                    if (end > index) {
                        try {
                            double depth = Double.parseDouble(line.substring(index, end));
                            index = end;

                            // 解析流速
                            while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
                                index++;
                            }
                            end = index;
                            while (end < line.length() && !Character.isWhitespace(line.charAt(end))) {
                                end++;
                            }
                            if (end > index) {
                                double speed = Double.parseDouble(line.substring(index, end));
                                index = end;

                                // 解析流向
                                while (index < line.length() && Character.isWhitespace(line.charAt(index))) {
                                    index++;
                                }
                                end = index;
                                while (end < line.length() && !Character.isWhitespace(line.charAt(end))) {
                                    end++;
                                }
                                if (end > index) {
                                    double direction = Double.parseDouble(line.substring(index, end));

                                    // 添加到列表
                                    yList.add(depth);
                                    x1List.add(speed);
                                    x2List.add(direction);
                                }
                            }
                        } catch (NumberFormatException e) {
                            // 忽略格式错误的数据行
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("获取指定时间数据失败：" + e.getMessage());
        }

        sortDatByDepth(result);
        return result;
    }


    /**
     * 解析空格分隔的数据行，提取海流数据信息
     * 适配格式如："3     2.5      2.40   227.0"的空格分隔数据
     *
     * @param line 包含海流数据的字符串行，应包含4个字段：标识符、观测水深、水平流速、水平流向
     * @return 解析成功的SeaCurrentData对象，解析失败或数据格式异常时返回null
     */
    private SeaCurrentData parseDataLine(String line) {
        SeaCurrentData data = new SeaCurrentData();
        try {
            // 按多个空格分割数据行（适配不同数量的空格）
            String[] dataParts = line.split("\\s+");
            // 数据行标准格式：3 观测水深 水平流速 水平流向（共4个字段）
            if (dataParts.length == 4) {
                // 观测水深：第2个字段（如 "2.5"）
                data.setObservationDepth(Double.parseDouble(dataParts[1].trim()));
                // 水平流速：第3个字段（如 "2.40"）
                data.setHorizontalSpeed(Double.parseDouble(dataParts[2].trim()));
                // 水平流向：第4个字段（如 "227.0"）
                data.setHorizontalDirection(Double.parseDouble(dataParts[3].trim()));
                // 质控符：若文件中未显式标注，默认填空格（表示无误）
                data.setSpeedQcFlag(" ");
                data.setDirectionQcFlag(" ");
            } else {
                // 字段数量异常，返回null（过滤无效行）
                return null;
            }
        } catch (NumberFormatException e) {
            // 数字格式异常（如非数字），返回null（过滤无效行）
            return null;
        }
        return data;
    }


    /**
     * 辅助方法：判断数据是否有效（按文档质控符规则）
     *
     * @param data 海流数据对象
     * @return true=有效，false=无效
     */
    private boolean isDataValid(SeaCurrentData data) {
        // 质控符为空格（无误），且核心数值非空
        return " ".equals(data.getSpeedQcFlag())
                && " ".equals(data.getDirectionQcFlag())
                && data.getObservationDepth() != null
                && data.getHorizontalSpeed() != null
                && data.getHorizontalDirection() != null;
    }



    /**
     * 辅助方法：按水深升序排序，保持流速、流向与水深的对应关系
     *
     * @param dataMap 包含水深（YList）、流速（X1List）、流向（X2List）列表的映射
     */
    private void sortDatByDepth(Map<String, Object> dataMap) {
        // 从Map中获取数据列表并进行类型转换（适配Map<String, Object>）
        List<Double> depthList = (List<Double>) dataMap.get("YList");
        List<Double> speedList = (List<Double>) dataMap.get("X1List");
        List<Double> directionList = (List<Double>) dataMap.get("X2List");

        // 校验列表有效性
        if (depthList == null || speedList == null || directionList == null) {
            throw new IllegalArgumentException("排序失败：数据列表（YList/X1List/X2List）不能为空");
        }
        if (!(depthList.size() == speedList.size() && speedList.size() == directionList.size())) {
            throw new IllegalArgumentException("排序失败：水深、流速、流向列表长度不一致");
        }

        // 构建三元组列表（绑定水深-流速-流向）
        List<DataTriplet> triplets = new ArrayList<>();
        for (int i = 0; i < depthList.size(); i++) {
            triplets.add(new DataTriplet(
                    depthList.get(i),
                    speedList.get(i),
                    directionList.get(i)
            ));
        }

        // 按水深升序排序（浅水深在前，对应Y轴上方）
        triplets.sort(Comparator.comparingDouble(DataTriplet::getDepth));

        // 清空原列表，填入排序后的数据（确保一一对应）
        depthList.clear();
        speedList.clear();
        directionList.clear();
        for (DataTriplet triplet : triplets) {
            depthList.add(triplet.getDepth());
            speedList.add(triplet.getSpeed());
            directionList.add(triplet.getDirection());
        }
    }

    /**
     * 辅助方法：安全截取字符串（避免下标越界）
     *
     * @param line       原始字符串
     * @param startIndex 起始下标（0开始）
     * @param length     截取长度
     * @return 截取后的字符串，越界返回空字符串
     */
    private String substringToString(String line, int startIndex, int length) {
        if (startIndex >= line.length()) return "";
        int endIndex = Math.min(startIndex + length, line.length());
        return line.substring(startIndex, endIndex);
    }

    /**
     * 读取TXT中所有不重复的时间（从类型3行提取，保持原始格式）
     * @param filePath TXT文件绝对路径
     * @return 按时间出现顺序排列的无重复时间列表（保持文件中的原始格式）
     */
    public List<String> readTxtBySeaCurrentTime(String filePath) {
        Set<String> timeSet = new LinkedHashSet<>(); // 用LinkedHashSet保证顺序+去重

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || !line.startsWith("3")) {
                    continue; // 只处理类型3行
                }

                // 解析类型3行中的时间（保持原始格式）
                String[] parts = line.split("\\s+");
                if (parts.length >= 2) {
                    String rawTime = parts[1].trim();
                    // 校验时间格式（数字）
                    if (isNumeric(rawTime)) {
                        timeSet.add(rawTime);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("读取时间列表失败：" + e.getMessage(), e);
        }

        // 转为List返回（保持插入顺序，即文件中时间出现顺序）
        return new ArrayList<>(timeSet);
    }

    /**
     * 根据指定时间，获取TXT中对应的数据（水深-流速-流向）
     * @param filePath   TXT文件绝对路径
     * @param targetTime 目标时间
     * @return 包含水深、流速、流向的映射（YList=水深，X1List=流速，X2List=流向），附加中文名称(header)和单位(unit)
     */
    public Map<String, Object> readTxtBySeaCurrentTimeSeries(String filePath, String targetTime) {
        // 初始化结果容器
        Map<String, Object> result = new HashMap<>();
        List<Double> depthList = new ArrayList<>();   // 水深列表
        List<Double> speedList = new ArrayList<>();   // 流速列表
        List<Double> directionList = new ArrayList<>();// 流向列表

        // 1. 添加头部信息（中文名称映射）
        Map<String, String> header = new HashMap<>();
        header.put("Y", "水深");
        header.put("X1", "流速");
        header.put("X2", "流向");
        result.put("header", header);

        // 2. 添加单位信息（与中文名称一一对应，使用海洋水文常用单位）
        Map<String, String> unit = new HashMap<>();
        unit.put("Y", "(m)");    // 水深单位：米
        unit.put("X1", "(m/s)");// 流速单位：米/秒
        unit.put("X2", "(°)");    // 流向单位：度（地理方位角）
        result.put("unit", unit);

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || !line.startsWith("3")) {
                    continue; // 只处理类型3行
                }

                // 解析类型3行：3 [原始时间] [水深] [流速] [流向]
                DmCurrentData data = parseDmDataLine(line);
                if (data == null || !isDmDataValid(data)) {
                    continue; // 跳过无效数据
                }

                // 提取当前行的原始时间
                String[] parts = line.split("\\s+");
                if (parts.length < 2) continue;
                String currentTime = parts[1].trim();

                // 匹配目标时间，收集对应数据
                if (currentTime.equals(targetTime)) {
                    depthList.add(data.getObservationDepth());
                    speedList.add(data.getHorizontalSpeed());
                    directionList.add(data.getHorizontalDirection());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("获取指定时间数据失败：" + e.getMessage(), e);
        }

        // 按水深升序排序（确保Y轴“越上越浅”，且数据一一对应）
        sortDataByDepth(depthList, speedList, directionList);

        // 填充结果数据列表
        result.put("YList", depthList);
        result.put("X1List", speedList);
        result.put("X2List", directionList);
        return result;
    }

    /**
     * 解析类型3的数据行（格式：3 20030601000000 5.0 14.00 160.0）
     * @param line 原始类型3行
     * @return 解析后的DmCurrentData对象（无效行返回null）
     */
    private DmCurrentData parseDmDataLine(String line) {
        DmCurrentData data = new DmCurrentData();
        try {
            String[] parts = line.split("\\s+");
            // 类型3行需至少5列：3 + 时间 + 水深 + 流速 + 流向
            if (parts.length < 5) {
                return null;
            }

            // 解析字段（对应实际格式）
            double depth = Double.parseDouble(parts[2].trim());       // 第3列：水深
            double speed = Double.parseDouble(parts[3].trim());       // 第4列：流速
            double direction = Double.parseDouble(parts[4].trim());   // 第5列：流向

            // 设置数据（质控符默认空格，表示有效）
            data.setObservationDepth(depth);
            data.setHorizontalSpeed(speed);
            data.setHorizontalDirection(direction);
            data.setDepthQcFlag(" ");
            data.setSpeedQcFlag(" ");
            data.setDirectionQcFlag(" ");

        } catch (NumberFormatException e) {
            // 数字格式错误（如流速为非数字），返回null
            return null;
        }
        return data;
    }

    /**
     * 校验数据有效性（质控符为空格 + 核心数值非空）
     * @param data 解析后的DmCurrentData对象
     * @return true=有效，false=无效
     */
    private boolean isDmDataValid(DmCurrentData data) {
        return " ".equals(data.getDepthQcFlag())
                && " ".equals(data.getSpeedQcFlag())
                && " ".equals(data.getDirectionQcFlag())
                && data.getObservationDepth() != null
                && data.getHorizontalSpeed() != null
                && data.getHorizontalDirection() != null;
    }

    /**
     * 按水深升序排序，保持水深-流速-流向的对应关系
     * @param depthList    水深列表
     * @param speedList    流速列表
     * @param directionList 流向列表
     */
    private void sortDataByDepth(List<Double> depthList, List<Double> speedList, List<Double> directionList) {
        // 校验列表长度一致性
        if (depthList.size() != speedList.size() || speedList.size() != directionList.size()) {
            throw new IllegalArgumentException("数据列表长度不一致，无法排序");
        }

        // 构建“水深-流速-流向”三元组，用于排序
        List<DataTriplet> triplets = new ArrayList<>();
        for (int i = 0; i < depthList.size(); i++) {
            triplets.add(new DataTriplet(
                    depthList.get(i),
                    speedList.get(i),
                    directionList.get(i)
            ));
        }

        // 按水深升序排序（浅水深在前）
        triplets.sort(Comparator.comparingDouble(DataTriplet::getDepth));

        // 清空原列表，填入排序后的数据
        depthList.clear();
        speedList.clear();
        directionList.clear();
        for (DataTriplet triplet : triplets) {
            depthList.add(triplet.getDepth());
            speedList.add(triplet.getSpeed());
            directionList.add(triplet.getDirection());
        }
    }

    /**
     * 校验字符串是否为纯数字（用于时间格式校验）
     * @param str 待校验字符串
     * @return true=纯数字，false=非纯数字
     */
    private boolean isNumeric(String str) {
        if (str == null || str.isEmpty()) return false;
        for (char c : str.toCharArray()) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 辅助类：存储“水深-流速-流向”三元组，用于排序
     */
    private static class DataTriplet {
        private final double depth;    // 水深
        private final double speed;    // 流速
        private final double direction;// 流向

        public DataTriplet(double depth, double speed, double direction) {
            this.depth = depth;
            this.speed = speed;
            this.direction = direction;
        }

        public double getDepth() { return depth; }
        public double getSpeed() { return speed; }
        public double getDirection() { return direction; }
    }

    // 平滑方法枚举
    public enum SmoothMethod {
        GAUSSIAN, INTERPOLATION, NONE
    }

    // 时间单位信息封装类
    private static class TimeUnitInfo {
        private final Date referenceDate;
        private final long millisPerUnit;

        public TimeUnitInfo(Date referenceDate, long millisPerUnit) {
            this.referenceDate = referenceDate;
            this.millisPerUnit = millisPerUnit;
        }

        public long convertToMilliseconds(double timeValue) {
            return (long) (timeValue * millisPerUnit);
        }
    }




    /**
     * 读取OSF通用时间文件，返回去重后的原始时间列表
     *
     * @param filePath 时间文件路径
     * @return 去重后的原始时间字符串列表
     */
    public List<String> readOsfUniversalTime(String filePath) {
        List<String> rawTimeList = new ArrayList<>();
        Set<String> timeDuplicateSet = new HashSet<>();

        // 读取文件并解析时间数据
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || !line.startsWith("2")) {
                    continue;
                }
                // 优化：提取12-14位时间（覆盖所有格式）
                String rawTime = extractRawTimeFromType2(line);
                if (rawTime != null && !rawTime.isEmpty() && !timeDuplicateSet.contains(rawTime)) {
                    timeDuplicateSet.add(rawTime);
                    rawTimeList.add(rawTime);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("读取时间列表失败：" + e.getMessage());
        }

        return rawTimeList;
    }


    /**
     * 读取与目标时间匹配的剖面数据
     *
     * @param filePath   剖面文件路径，支持UTF-8编码格式
     * @param targetTime 目标时间字符串，用于匹配剖面数据中的时间标识（格式需与文件中一致）
     * @return 返回包含剖面数据及相关信息的Map对象：
     *         - "header": 字段名称映射（如Y:水深）
     *         - "unit": 字段单位映射（如Y:(m)）
     *         - "profileHeader": 匹配到的剖面头信息（GtsppGeneralHeader对象）
     *         - "YList": 水深列表（按降序排列）
     *         - "X1List": 温度列表
     *         - "X2List": 盐度列表（可能包含null值表示缺失）
     */
    public Map<String, Object> readOsfUniversalProfile(String filePath, String targetTime) {
        Map<String, Object> result = new HashMap<>();
        List<Double> yList = new ArrayList<>(); // 水深
        List<Double> x1List = new ArrayList<>(); // 温度
        List<Double> x2List = new ArrayList<>(); // 盐度（可选）
        GtsppGeneralHeader targetHeader = new GtsppGeneralHeader();

        // 固定头部和单位信息
        Map<String, String> header = new HashMap<>();
        header.put("Y", "水深");
        header.put("X1", "温度");
        header.put("X2", "盐度");
        result.put("header", header);

        Map<String, String> unit = new HashMap<>();
        unit.put("Y", "(m)");
        unit.put("X1", "(℃)");
        unit.put("X2", ""); // 实用盐度单位
        result.put("unit", unit);

        boolean isTargetProfile = false;
        int currentDataCount = 0;
        int expectedDataCount = 0;

        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty()) {
                    continue;
                }

                // 1. 解析类型1行：数据源（兼容“前缀空格”和“多空格分隔”）
                if (line.startsWith("1")) {
                    targetHeader.setDataSource(extractDataSourceFromType1(line));
                    targetHeader.setRawHeader(buildRawHeader(targetHeader.getRawHeader(), "1行", line));
                }
                // 2. 解析类型2行：匹配目标时间，提取经纬度（兼容12/14位时间）
                else if (line.startsWith("2")) {
                    String rawTime = extractRawTimeFromType2(line);
                    String latitude = extractLatitudeFromType2(line);
                    String longitude = extractLongitudeFromType2(line);

                    // 时间完全匹配则标记为目标剖面
                    if (targetTime.equals(rawTime)) {
                        isTargetProfile = true;
                        targetHeader.setRawTime(rawTime);
                        targetHeader.setLatitude(latitude);
                        targetHeader.setLongitude(longitude);
                    } else {
                        isTargetProfile = false;
                    }
                    targetHeader.setRawHeader(buildRawHeader(targetHeader.getRawHeader(), "2行", line));
                }
                // 3. 解析类型3行：数据点数量（优化：取第一个有效数字，兼容多格式）
                else if (isTargetProfile && line.startsWith("3")) {
                    expectedDataCount = extractDataCountFromType3(line);
                    targetHeader.setDataCount(expectedDataCount);
                    targetHeader.setRawHeader(buildRawHeader(targetHeader.getRawHeader(), "3行", line));
                    currentDataCount = 0;
                    // 重置数据列表，避免污染
                    yList.clear();
                    x1List.clear();
                    x2List.clear();
                }
                // 4. 解析类型7行：深度/温度/盐度（优化：兼容“无盐度”和“多空格”）
                else if (isTargetProfile && line.startsWith("7") && expectedDataCount > 0) {
                    GtsppGeneralData data = extractDataFromType7(line);
                    if (data != null) {
                        yList.add(data.getDepth());
                        x1List.add(data.getTemperature());
                        // 兼容无盐度场景（如第三种格式）
                        if (data.getSalinity() != null) {
                            x2List.add(data.getSalinity());
                        } else {
                            x2List.add(null); // 用null标记无数据，避免列表长度不匹配
                        }
                        currentDataCount++;
                    }

                    // 数据采集完成，退出循环（避免后续数据干扰）
                    if (currentDataCount >= expectedDataCount) {
                        isTargetProfile = false;
                        break;
                    }
                }
            }

            // 验证结果（优化：允许盐度列表有null，但水深/温度必须非空）
            validateParseResult(yList, x1List, targetTime);

            // 深度降序排序（保持原逻辑，兼容null盐度）
            sortProfileByDepthDesc(yList, x1List, x2List);

            // 组装结果
            result.put("profileHeader", targetHeader);
            result.put("YList", yList);
            result.put("X1List", x1List);
            result.put("X2List", x2List);

        } catch (IOException e) {
            throw new RuntimeException("获取剖面数据失败：" + e.getMessage());
        }

        return result;
    }


    // ------------------- 核心解析方法（全部优化） -------------------

    /**
     * 从类型1格式的行中提取数据源信息
     * 类型1行数据源提取（兼容"前缀空格"和"多空格分隔"）
     *
     * @param line 类型1格式的输入行字符串
     * @return 提取的数据源字符串，如果无法提取则返回"Unknown"
     */
    private String extractDataSourceFromType1(String line) {
        // 分割所有非空字段（用\\s+匹配1个以上空格）
        String[] parts = line.split("\\s+");

        // 类型1行格式：1 [数据源] ...，取第一个非空字段后的内容（排除"1"）
        if (parts.length >= 2) {
            // 兼容"数据源含空格"（如"US ARGO"），从第2个字段开始拼接
            StringBuilder sb = new StringBuilder();
            for (int i = 1; i < parts.length; i++) {
                if (!parts[i].isEmpty()) {
                    sb.append(parts[i]).append(" ");
                }
            }
            return sb.toString().trim();
        }
        return "Unknown";
    }


    /**
     * 从类型2行中提取原始时间字符串
     * <p>
     * 该方法用于从给定的字符串行中提取符合OSF时间格式的时间戳，
     * 支持12-14位连续数字的匹配，覆盖所有可能的时间格式。
     *
     * @param line 包含时间信息的字符串行
     * @return 提取到的时间字符串，如果未找到匹配项则返回null
     */
    private String extractRawTimeFromType2(String line) {
        // 正则：匹配连续12-14位数字（OSF时间的核心特征）
        Pattern timePattern = Pattern.compile("\\b\\d{12,14}\\b");
        Matcher matcher = timePattern.matcher(line);
        // 取第一个匹配项（类型2行中时间唯一）
        if (matcher.find()) {
            return matcher.group().trim();
        }
        return null;
    }


    /**
     * 从类型2格式的字符串中提取纬度信息
     * 该方法专门处理格式如"929.16S1202436.01E"的字符串，兼容无空格分隔的场景
     *
     * @param line 包含经纬度信息的原始字符串
     * @return 提取到的纬度信息，格式为"数字+N/S"，如"929.16S"；如果未找到则返回"Unknown"
     */
    private String extractLatitudeFromType2(String line) {
        // 纬度正则：数字+N/S（兼容带小数点，如929.16S、8.40S）
        Pattern latPattern = Pattern.compile("(\\d+\\.?\\d+)[NS]");
        Matcher matcher = latPattern.matcher(line);
        if (matcher.find()) {
            return matcher.group().trim(); // 返回"929.16S"格式的纬度信息
        }
        return "Unknown";
    }


    /**
     * 从类型2格式的字符串中提取经度信息
     * @param line 包含经纬度信息的字符串行
     * @return 提取到的经度字符串，格式如"1202436.01E"，如果未找到则返回"Unknown"
     */
    private String extractLongitudeFromType2(String line) {
        // 经度正则：数字+E/W（兼容带小数点，如1202436.01E、0122425.20E）
        Pattern lonPattern = Pattern.compile("(\\d+\\.?\\d+)[EW]");
        Matcher matcher = lonPattern.matcher(line);
        // 经纬度成对出现，取第二个匹配项（第一个是纬度，第二个是经度）
        int count = 0;
        while (matcher.find()) {
            count++;
            if (count == 2) {
                return matcher.group().trim();
            }
        }
        return "Unknown";
    }


    /**
     * 从类型3的行数据中提取数据量信息
     * 该函数用于解析以"3"开头的行数据，提取其中包含的第一个有效数字作为数据量
     * @param line 待解析的类型3行数据字符串
     * @return 提取到的数据量，如果未找到有效数字则返回0
     */
    private int extractDataCountFromType3(String line) {
        // 匹配所有数字（排除“3”本身）
        Pattern countPattern = Pattern.compile("\\b\\d+\\b");
        Matcher matcher = countPattern.matcher(line);
        int count = 0;
        while (matcher.find()) {
            String numStr = matcher.group().trim();
            // 跳过“3”（行首标识），取第一个非“3”的数字
            if (!"3".equals(numStr)) {
                try {
                    return Integer.parseInt(numStr);
                } catch (NumberFormatException e) {
                    continue;
                }
            }
        }
        return 0;
    }


    /**
     * 从类型7的行数据中提取海洋观测数据
     * <p>
     * 该方法用于解析GTSPP格式中类型7的数据行，提取水深、温度和盐度信息。
     * 支持处理无盐度数据的情况以及字段间存在多个空格的格式。
     *
     * @param line 包含类型7数据的字符串行，格式如："7 100.0 25.0 35.0" 或 "7 100.0 25.0"
     * @return 解析成功的GtsppGeneralData对象，包含深度、温度和盐度数据；
     *         如果数据格式不正确或字段不足，则返回null
     */
    private GtsppGeneralData extractDataFromType7(String line) {
        GtsppGeneralData data = new GtsppGeneralData();
        // 分割所有非空字段（用\\s+匹配多空格）
        String[] parts = line.split("\\s+");

        // 有效数据格式：至少包含"7" + "水深" + "温度" 三个部分
        if (parts.length < 3) {
            return null; // 字段不足，返回无效数据
        }

        try {
            // 第1个字段是"7"（标识），第2个字段是水深
            data.setDepth(Double.parseDouble(parts[1]));
            // 第3个字段是温度
            data.setTemperature(Double.parseDouble(parts[2]));
            // 第4个字段（如果存在）是盐度
            if (parts.length >= 4) {
                data.setSalinity(Double.parseDouble(parts[3]));
            } else {
                data.setSalinity(null); // 无盐度数据
            }
            return data;
        } catch (NumberFormatException e) {
            // 数字格式错误，返回无效数据
            return null;
        }
    }


    // ------------------- 辅助方法 -------------------

    /**
     * 构建原始头信息
     *
     * @param existingRawHeader 已存在的原始头信息
     * @param lineType 行类型
     * @param line 头信息行内容
     * @return 构建后的原始头信息字符串
     */
    private String buildRawHeader(String existingRawHeader, String lineType, String line) {
        // 如果已存在的头信息为空，则直接构建新的头信息
        if (existingRawHeader == null || existingRawHeader.isEmpty()) {
            return lineType + "：" + line;
        } else {
            // 如果已存在头信息，则将新信息追加到现有信息后面
            return existingRawHeader + "| " + lineType + "：" + line;
        }
    }


    /**
     * 验证解析结果（允许盐度为null，但水深/温度必须非空）
     *
     * @param depthList 水深数据列表
     * @param tempList 温度数据列表
     * @param targetTime 目标时间
     * @throws IllegalArgumentException 当数据验证失败时抛出异常
     */
    private void validateParseResult(List<Double> depthList, List<Double> tempList, String targetTime) {
        if (depthList.isEmpty() || tempList.isEmpty()) {
            throw new IllegalArgumentException("未找到目标时间[" + targetTime + "]对应的有效数据，可能原因：" +
                    "1. 目标时间与文件中格式不匹配（需与类型2行时间完全一致，如12位/14位）；" +
                    "2. 该时间点无类型7行数据；" +
                    "3. 文件格式异常（如类型3行数据量为0）。");
        }
        // 验证水深和温度列表长度一致
        if (depthList.size() != tempList.size()) {
            throw new IllegalArgumentException("解析异常：水深与温度数据量不匹配（目标时间：" + targetTime + "）");
        }
    }


    /**
     * 按深度降序排序（兼容盐度为null的场景）
     *
     * @param depthList 深度数据列表，不能为null，列表中的元素不能为null
     * @param tempList 温度数据列表，不能为null，列表中的元素不能为null
     * @param salList 盐度数据列表，不能为null，列表中的元素可以为null
     *
     * @throws IllegalArgumentException 当三个列表长度不一致，或深度、温度数据包含null值时抛出
     */
    private void sortProfileByDepthDesc(List<Double> depthList, List<Double> tempList, List<Double> salList) {
        // 验证列表长度一致性
        if (depthList.size() != tempList.size() || depthList.size() != salList.size()) {
            throw new IllegalArgumentException("数据列表长度不匹配，无法排序");
        }

        List<GtsppDataTriplet> triplets = new ArrayList<>();
        for (int i = 0; i < depthList.size(); i++) {
            // 安全获取各值，避免null值导致问题
            Double depth = depthList.get(i);
            Double temp = tempList.get(i);
            Double sal = salList.get(i);

            // 验证必要数据不为null
            if (depth == null || temp == null) {
                throw new IllegalArgumentException("水深或温度数据不能为空，索引位置：" + i);
            }

            triplets.add(new GtsppDataTriplet(depth, temp, sal));
        }

        // 按水深降序排序（只使用深度进行比较，避免涉及可能为null的盐度）
        triplets.sort((a, b) -> Double.compare(b.getDepth(), a.getDepth()));

        // 重新填充列表
        depthList.clear();
        tempList.clear();
        salList.clear();
        for (GtsppDataTriplet triplet : triplets) {
            depthList.add(triplet.getDepth());
            tempList.add(triplet.getTemperature());
            salList.add(triplet.getSalinity()); // 允许盐度为null
        }
    }





        /**
     * 读取NetCDF文件中date(6,240)变量的时间解析方法（适配年/月/日/时/分/秒矩阵结构）
     *
     * @param filePath NetCDF文件路径
     * @param year     指定年份，用于筛选月份数据；若为null，则默认取第一个年份
     * @param month    指定月份，用于筛选具体时间详情；若为null，则默认取指定年份的第一个月份
     * @return 返回包含时间信息的Map对象，结构如下：
     *         - "years": 所有年份列表（有序）
     *         - "months": 指定年份下的月份列表（有序）
     *         - "days": 指定年月下的时间详情列表，每个元素为Map，包含：
     *             - "day": 日期（dd格式）
     *             - "formattedTime": 完整时间字符串（yyyy-MM-dd HH:mm:ss）
     *             - "index": 原始数组下标（0~239）
     *             - "hour", "minute", "second": 时、分、秒字段（HH/mm/ss格式）
     * @throws IOException 当文件读取失败、变量不存在或维度不匹配时抛出异常
     */
    @Override
    public Map<String, Object> readDateVariableByMat(String filePath, Integer year, Integer month) throws IOException {
        Map<String, Object> result = new HashMap<>();
        Set<String> yearSet = new TreeSet<>(); // 存储所有年份（有序）
        Map<String, Set<String>> monthMap = new TreeMap<>(); // 年份→月份集合（有序）
        Map<String, List<Map<String, Object>>> dayMap = new TreeMap<>(); // 年月键→时间详情列表（有序）

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 查找date变量（精准匹配，与实际变量名一致）
            Variable dateVar = ncFile.findVariable("date");
            if (dateVar == null) {
                // 异常提示中包含文件内所有变量名，便于定位问题
                String allVars = ncFile.getVariables().stream()
                        .map(Variable::getShortName)
                        .collect(Collectors.joining(", "));
                throw new IOException("NC文件中未找到\"date\"变量（文件内所有变量：" + allVars + "）");
            }

            // 2. 验证date变量维度（必须为6×240，与实际结构匹配）
            int[] dateDims = dateVar.getDimensions().stream()
                    .mapToInt(Dimension::getLength)
                    .toArray();
            if (dateDims.length != 2 || dateDims[0] != 6 || dateDims[1] != 240) {
                throw new IOException("date变量维度与预期不符！实际维度：" + Arrays.toString(dateDims) +
                        "，预期维度：[6, 240]（年/月/日/时/分/秒 × 240个时间点）");
            }

            // 3. 读取date矩阵数据（6行×240列）
            Array dateArray = dateVar.read();
            Index dateIndex = dateArray.getIndex();
            SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
            SimpleDateFormat monthFormat = new SimpleDateFormat("MM");
            SimpleDateFormat fullTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            // 4. 遍历240个时间点（列索引：0-239）
            for (int timeIdx = 0; timeIdx < 240; timeIdx++) {
                // 读取当前时间点的6个字段：年(0)、月(1)、日(2)、时(3)、分(4)、秒(5)
                double yearVal = dateArray.getDouble(dateIndex.set(0, timeIdx));
                double monthVal = dateArray.getDouble(dateIndex.set(1, timeIdx));
                double dayVal = dateArray.getDouble(dateIndex.set(2, timeIdx));
                double hourVal = dateArray.getDouble(dateIndex.set(3, timeIdx));
                double minuteVal = dateArray.getDouble(dateIndex.set(4, timeIdx));
                double secondVal = dateArray.getDouble(dateIndex.set(5, timeIdx));

                // 转换为整数（避免浮点误差，如1997.0→1997）
                int yearInt = (int) yearVal;
                int monthInt = (int) monthVal;
                int dayInt = (int) dayVal;
                int hourInt = (int) hourVal;
                int minuteInt = (int) minuteVal;
                int secondInt = (int) secondVal;

                // 校验时间合法性（避免无效值，如月份>12、日期>31）
                if (!isValidDate(yearInt, monthInt, dayInt)) {
                    System.out.printf("时间点%d（index=%d）无效：%d-%d-%d，已跳过%n",
                            timeIdx + 1, timeIdx, yearInt, monthInt, dayInt);
                    continue;
                }

                // 构建Date对象（适配SimpleDateFormat格式化）
                Calendar calendar = Calendar.getInstance();
                calendar.set(yearInt, monthInt - 1, dayInt, hourInt, minuteInt, secondInt);
                calendar.set(Calendar.MILLISECOND, 0); // 清除毫秒，避免格式干扰
                Date actualDate = calendar.getTime();

                // 格式化时间字符串（统一格式：yyyy-MM-dd HH:mm:ss）
                String fullTimeStr = fullTimeFormat.format(actualDate);
                String yearStr = yearFormat.format(actualDate); // 年份：yyyy
                String monthStr = monthFormat.format(actualDate); // 月份：MM（补0，如1→01）

                // 5. 构建层级化数据（与sunReadValidTime格式完全对齐）
                // 5.1 加入年份集合
                yearSet.add(yearStr);

                // 5.2 加入月份映射（年份→月份集合）
                monthMap.computeIfAbsent(yearStr, k -> new TreeSet<>()).add(monthStr);

                // 5.3 构建年月键（如"1997-01"），加入时间详情列表
                String yearMonthKey = yearStr + "-" + monthStr;
                Map<String, Object> timeInfo = new HashMap<>();
                timeInfo.put("day", String.format("%02d", dayInt)); // 日期：dd（补0）
                timeInfo.put("formattedTime", fullTimeStr); // 完整格式化时间
                timeInfo.put("index", timeIdx); // 原始数组下标（0-239）
                // 补充时分秒信息（可选，便于业务扩展）
                timeInfo.put("hour", String.format("%02d", hourInt));
                timeInfo.put("minute", String.format("%02d", minuteInt));
                timeInfo.put("second", String.format("%02d", secondInt));

                dayMap.computeIfAbsent(yearMonthKey, k -> new ArrayList<>()).add(timeInfo);
            }

            // 6. 组装返回结果（与sunReadValidTime结构一致，支持年/月筛选）
            // 6.1 设置所有年份列表
            result.put("years", new ArrayList<>(yearSet));

            // 6.2 根据传入的year参数，筛选对应的月份（无year则取第一个年份）
            String selectedYear = year != null ? String.valueOf(year) :
                    (yearSet.isEmpty() ? null : yearSet.iterator().next());
            if (selectedYear != null && monthMap.containsKey(selectedYear)) {
                // 设置选中年份的月份列表
                result.put("months", new ArrayList<>(monthMap.get(selectedYear)));

                // 6.3 根据传入的month参数，筛选对应的时间详情（无month则取第一个月份）
                String selectedMonth = month != null ? String.format("%02d", month) :
                        (monthMap.get(selectedYear).isEmpty() ? null : monthMap.get(selectedYear).iterator().next());
                if (selectedMonth != null) {
                    String selectedYearMonthKey = selectedYear + "-" + selectedMonth;
                    // 设置选中年月的时间详情列表
                    result.put("days", dayMap.getOrDefault(selectedYearMonthKey, Collections.emptyList()));
                } else {
                    result.put("days", Collections.emptyList());
                }
            } else {
                result.put("months", Collections.emptyList());
                result.put("days", Collections.emptyList());
            }

            System.out.printf("成功解析date变量：共%d个有效时间点，年份范围：%s~%s%n",
                    dayMap.values().stream().mapToInt(List::size).sum(),
                    yearSet.isEmpty() ? "无" : Collections.min(yearSet),
                    yearSet.isEmpty() ? "无" : Collections.max(yearSet));

        } catch (Exception e) {
            // 补充文件路径上下文，便于定位错误
            throw new IOException("解析date(6,240)变量失败，文件路径：" + filePath, e);
        }

        return result;
    }


// -------------------------- 辅助方法 --------------------------
    /**
     * 校验日期合法性（避免月份>12、日期>当月最大天数等无效情况）
     * @param year 年份，有效范围为1997-2016
     * @param month 月份，有效范围为1-12
     * @param day 日期，有效范围根据月份和年份确定
     * @return true表示日期合法，false表示日期不合法
     */
    private boolean isValidDate(int year, int month, int day) {
        if (year < 1997 || year > 2016) { // 数据集时间范围：1997-2016
            return false;
        }
        if (month < 1 || month > 12) { // 月份1-12
            return false;
        }
        // 校验日期（根据月份判断最大天数，简化版，覆盖数据集场景）
        int maxDay;
        switch (month) {
            case 2:
                // 判断闰年（能被4整除且不能被100整除，或能被400整除）
                boolean isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
                maxDay = isLeapYear ? 29 : 28;
                break;
            case 4: case 6: case 9: case 11:
                maxDay = 30;
                break;
            default:
                maxDay = 31;
                break;
        }
        return day >= 1 && day <= maxDay;
    }









    /**
     * 读取NetCDF文件中深度变量的30个层级信息（仅返回depth(30,324,288)的第一维层级）
     * @param filePath NetCDF文件路径
     * @return 包含30个深度层级的Map列表（仅含下标、默认单位，无网格点数据）
     * @throws IOException 读取失败时抛出
     */
    @Override
    public List<Map<Object, Object>> processReadAllDepthLevel(String filePath) throws IOException {
        List<Map<Object, Object>> depthLevelList = new ArrayList<>();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 精准查找深度变量（必须为"depth"）
            Variable depthVar = ncFile.findVariable("depth");
            if (depthVar == null) {
                // 异常提示包含文件内所有变量名，便于定位问题
                String allVars = getVariableNames(ncFile);
                throw new IOException("NC文件中未找到名为\"depth\"的变量（文件内所有变量：" + allVars + "）");
            }

            // 2. 验证深度变量维度（核心：第一维必须为30，匹配depth(30,324,288)）
            int[] depthDims = depthVar.getDimensions().stream()
                    .mapToInt(Dimension::getLength)
                    .toArray();
            // 仅校验第一维为30，不强制校验后两维（兼容可能的维度顺序微小差异，但确保核心层级数正确）
            if (depthDims.length < 1 || depthDims[0] != 30) {
                throw new IOException("深度变量第一维层级数与预期不符！实际维度：" + Arrays.toString(depthDims) +
                        "，预期第一维为30（对应depth(30,324,288)的30个深度层级）");
            }
            int depthLevelCount = depthDims[0]; // 固定为30

            // 3. 读取深度单位（优先读属性，无则用文档默认"m"）
            String depthUnits = getAttributeValueByMat(depthVar, "units", "m");

            // 4. 直接生成30个深度层级（仅基于第一维下标，不遍历后续网格维度）
            for (int depthIdx = 0; depthIdx < depthLevelCount; depthIdx++) {
                Map<Object, Object> depthInfo = new HashMap<>();
                depthInfo.put("index", depthIdx);          // 深度层级下标（0-29，共30个）
                depthInfo.put("units", depthUnits);        // 深度单位（文档默认"m"）
                depthInfo.put("dimensionDesc", "对应depth(30,324,288)的第" + (depthIdx + 1) + "个深度层级"); // 维度说明
                depthLevelList.add(depthInfo);
            }

            // 校验结果：确保恰好返回30个层级
            if (depthLevelList.size() != 30) {
                throw new IOException("深度层级生成异常！预期30个，实际生成" + depthLevelList.size() + "个");
            } else {
                System.out.printf("成功返回depth(30,324,288)的30个深度层级（下标0-29）%n");
            }

        } catch (Exception e) {
            throw new IOException("读取深度层级失败，文件路径：" + filePath, e);
        }

        return depthLevelList;
    }


// -------------------------- 辅助方法 --------------------------
    /**
     * 获取NC文件中所有变量名
     *
     * @param ncFile Netcdf文件对象
     * @return 以逗号分隔的变量名字符串
     */
    private String getVariableNames(NetcdfFile ncFile) {
        // 获取所有变量的短名称并用逗号连接成字符串
        return ncFile.getVariables().stream()
                .map(Variable::getShortName)
                .collect(Collectors.joining(", "));
    }


    /**
     * 读取变量属性值
     *
     * @param var 变量对象，用于查找属性
     * @param attrName 属性名称，要查找的属性名
     * @param defaultValue 默认值，当属性不存在或变量为空时返回此值
     * @return 返回指定属性的字符串值，如果属性不存在则返回默认值
     */
    private String getAttributeValueByMat(Variable var, String attrName, String defaultValue) {
        // 检查输入参数有效性，如果变量或属性名为null则返回默认值
        if (var == null || attrName == null) {
            return defaultValue;
        }
        // 查找并返回属性值，如果属性不存在则返回默认值
        ucar.nc2.Attribute attr = var.findAttribute(attrName);
        return attr != null ? attr.getStringValue() : defaultValue;
    }








    // 数据集固定参数（来自文档）
    private static final double MIN_LON = 117.01; // 最小经度：117.01°E
    private static final double MAX_LON = 131.66; // 最大经度：131.66°E
    private static final double MIN_LAT = 29.04; // 最小纬度：29.04°N
    private static final double MAX_LAT = 42.09; // 最大纬度：42.09°N
    private static final String CURRENT_UNIT = "m/s"; // 海流单位（文档明确）

    /**
     * 读取指定时间、深度层的海洋流场热力图数据（仅保留整数经纬度，且去重唯一记录）
     *
     * @param filePath   NetCDF文件路径
     * @param timeIndex  时间维度索引（从0开始）
     * @param depthIndex 深度维度索引（从0开始）
     * @param val        海洋流场变量名（如 "u" 或 "v"）
     * @return 包含流场数据列表及边界信息的Map对象，结构如下：
     *         {
     *             "data": List<OceanCurrentData>, // 去重后的流场数据列表
     *             "minLon": Double,               // 最小经度
     *             "maxLon": Double,               // 最大经度
     *             "minLat": Double,               // 最小纬度
     *             "maxLat": Double,               // 最大纬度
     *             "minVal": Double,               // 数据最小值
     *             "maxVal": Double,               // 数据最大值
     *             "unit": String                  // 数据单位
     *         }
     * @throws IOException 文件读取或解析异常时抛出
     */
    @Override
    public Map<String, Object> netcdfReadOceanCurrentHeatmap(
            String filePath, int timeIndex, int depthIndex, String val) throws IOException {

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取核心变量
            Variable latVar = ncFile.findVariable("lat");
            Variable lonVar = ncFile.findVariable("lon");
            Variable depthVar = ncFile.findVariable("depth");
            Variable timeVar = ncFile.findVariable("date");
            Variable dataVar = ncFile.findVariable(val);

            // 2. 验证变量存在性
            List<String> missingVars = new ArrayList<>();
            if (latVar == null) missingVars.add("lat");
            if (lonVar == null) missingVars.add("lon");
            if (depthVar == null) missingVars.add("depth");
            if (timeVar == null) missingVars.add("date");
            if (dataVar == null) missingVars.add(val);
            if (!missingVars.isEmpty()) {
                throw new IllegalArgumentException("缺失变量：" + String.join(", ", missingVars));
            }

            // 3. 验证索引合法性
            List<Dimension> dataDims = dataVar.getDimensions();
            if (dataDims.size() < 4) {
                throw new IllegalStateException("流场数据变量维度不足（需至少[时间,经度,纬度,深度]4维）");
            }
            int maxTimeIndex = dataDims.get(0).getLength() - 1; // 时间维度（第1维）
            int maxDepthIndex = dataDims.get(3).getLength() - 1; // 深度维度（第4维）

            if (timeIndex < 0 || timeIndex > maxTimeIndex) {
                throw new IllegalArgumentException("时间索引超出范围（0-" + maxTimeIndex + "）");
            }
            if (depthIndex < 0 || depthIndex > maxDepthIndex) {
                throw new IllegalArgumentException("深度索引超出范围（0-" + maxDepthIndex + "）");
            }

            // 4. 读取时间值（固定时间Index对应的时间）
            Array dateArray = timeVar.read();
            double currentTime = extractTime(dateArray, timeIndex);

            // 5. 读取经纬度（原始高精度数据）
            double[][] latArray = read2DArray(latVar);
            double[][] lonArray = read2DArray(lonVar);

            if (latArray.length != lonArray.length || latArray[0].length != lonArray[0].length) {
                throw new IllegalStateException("经纬度数组维度不一致（lon长度：" + lonArray.length + "，lat长度：" + latArray.length + "）");
            }
            int lonLen = lonArray.length; // 经度维度长度（对应data变量第2维）
            int latLen = lonArray[0].length; // 纬度维度长度（对应data变量第3维）

            // 6. 读取流场数据（指定时间+深度层）
            int[] origin = new int[]{timeIndex, 0, 0, depthIndex}; // 读取起始位置：[时间,经度起始,纬度起始,深度]
            int[] shape = new int[]{1, lonLen, latLen, 1}; // 读取范围：[1个时间,全部经度,全部纬度,1个深度]
            Array dataArray = dataVar.read(origin, shape);

            // 7. 处理数据类型转换（支持SHORT/DOUBLE类型）
            boolean isShortType = dataVar.getDataType().equals(DataType.SHORT);
            double scaleFactor = isShortType ? getAttributeDouble(dataVar, "scale_factor", 1.0) : 1.0;
            double addOffset = isShortType ? getAttributeDouble(dataVar, "add_offset", 0.0) : 0.0;
            short fillValue = isShortType ? getAttributeShort(dataVar, "_FillValue", (short) -9999) : 0;
            short missingValue = isShortType ? getAttributeShort(dataVar, "missing_value", (short) -9999) : 0;

            // 8. 初始化结果存储：用ConcurrentHashMap去重（key=经纬度组合，value=唯一记录）
            Map<String, OceanCurrentData> uniqueDataMap = new ConcurrentHashMap<>();
            AtomicReference<Double> minLat = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLat = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minLon = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxLon = new AtomicReference<>(Double.MIN_VALUE);
            AtomicReference<Double> minVal = new AtomicReference<>(Double.MAX_VALUE);
            AtomicReference<Double> maxVal = new AtomicReference<>(Double.MIN_VALUE);

            // 9. 读取深度数据（固定深度Index对应的深度值，确保所有记录深度一致）
            Array depthArray = depthVar.read();
            int[] depthDims = depthArray.getShape();
            double fixedDepth = getFixedDepth(depthArray, depthDims, depthIndex, latLen, lonLen);

            // 新增：设置数值过滤阈值（大于10000视为无效值）
            final double VALUE_THRESHOLD = 10000.0;

            // 10. 并行处理数据（去重逻辑核心 + 数值过滤）
            IntStream.range(0, lonLen)
                    .parallel() // 并行提升效率
                    .forEach(lonIdx -> {
                        for (int latIdx = 0; latIdx < latLen; latIdx++) {
                            // 10.1 获取原始经纬度并过滤无效范围
                            double rawLat = latArray[lonIdx][latIdx];
                            double rawLon = lonArray[lonIdx][latIdx];
                            if (!isValidGeoCoordinate(rawLat, rawLon)) {
                                continue;
                            }

                            // 10.2 经纬度舍入为整数（如32.1→32.0，129.9→130.0）
                            BigDecimal roundedLat = BigDecimal.valueOf(rawLat)
                                    .setScale(0, RoundingMode.HALF_UP);
                            BigDecimal roundedLon = BigDecimal.valueOf(rawLon)
                                    .setScale(0, RoundingMode.HALF_UP);

                            // 10.3 读取流场数据并处理无效值
                            Index dataIndex = dataArray.getIndex().set(0, lonIdx, latIdx, 0); // dataArray维度：[1,lonLen,latLen,1]
                            double currentVal;
                            if (isShortType) {
                                short rawVal = dataArray.getShort(dataIndex);
                                // 过滤填充值/缺失值
                                if (rawVal == fillValue || rawVal == missingValue) {
                                    continue;
                                }
                                currentVal = rawVal * scaleFactor + addOffset; // 还原真实值
                            } else {
                                currentVal = dataArray.getDouble(dataIndex);
                            }
                            // 新增：过滤NaN/无穷大/超过阈值的异常值
                            if (Double.isNaN(currentVal) || Double.isInfinite(currentVal) || currentVal > VALUE_THRESHOLD) {
                                continue;
                            }

                            // 10.4 生成唯一键：经纬度组合（确保相同坐标只保留1条）
                            String uniqueKey = roundedLat.toString() + "," + roundedLon.toString();

                            // 10.5 去重逻辑：putIfAbsent保留首次出现的记录
                            OceanCurrentData data = new OceanCurrentData();
                            data.setLat(roundedLat);
                            data.setLon(roundedLon);
                            data.setValue(BigDecimal.valueOf(currentVal));
                            data.setTime(BigDecimal.valueOf(currentTime));
                            data.setDepth(BigDecimal.valueOf(fixedDepth));
                            uniqueDataMap.putIfAbsent(uniqueKey, data);

                            // 10.6 更新边界值（基于去重后的唯一坐标和值）
                            updateBoundaryByMat(roundedLat.doubleValue(), minLat, maxLat);
                            updateBoundaryByMat(roundedLon.doubleValue(), minLon, maxLon);
                            updateBoundaryByMat(currentVal, minVal, maxVal);
                        }
                    });

            // 11. 转换为List并封装返回结果
            List<OceanCurrentData> dataList = new ArrayList<>(uniqueDataMap.values());
            return buildResultMap(dataList, minLon.get(), maxLon.get(),
                    minLat.get(), maxLat.get(),
                    minVal.get(), maxVal.get(),
                    CURRENT_UNIT);

        } catch (InvalidRangeException e) {
            throw new RuntimeException("数据读取范围错误（时间/深度索引可能超出变量维度）", e);
        }
    }




    // -------------------------- 辅助方法 --------------------------


        /**
     * 获取固定深度Index对应的深度值（支持三维深度[30,latLen,lonLen]和一维深度[30]）
     *
     * @param depthArray 深度数据数组
     * @param depthDims 深度数组的维度信息
     * @param depthIndex 深度层索引
     * @param latLen 纬度维度长度
     * @param lonLen 经度维度长度
     * @return 指定深度索引对应的深度值
     * @throws IllegalStateException 当深度数组维度不支持时抛出异常
     */
    private double getFixedDepth(Array depthArray, int[] depthDims, int depthIndex, int latLen, int lonLen) {
        // 三维深度：depth(30, latLen, lonLen) → 取该深度层第一个有效坐标的深度（同一层深度差异极小）
        if (depthDims.length == 3 && depthDims[0] == 30 && depthDims[1] == latLen && depthDims[2] == lonLen) {
            Index depthIndexObj = depthArray.getIndex().set(depthIndex, 0, 0); // 取(纬度0,经度0)的深度值
            return depthArray.getDouble(depthIndexObj);
        }
        // 一维深度：depth(30) → 直接取深度Index对应的值
        else if (depthDims.length == 1 && depthDims[0] == 30) {
            return depthArray.getDouble(depthIndex);
        }
        // 不支持的深度维度
        else {
            throw new IllegalStateException("不支持的深度变量维度（需三维[30,latLen,lonLen]或一维[30]，当前：" + Arrays.toString(depthDims) + "）");
        }
    }


    /**
     * 验证给定的经纬度坐标是否有效
     *
     * @param lat 纬度值，有效范围为MIN_LAT-0.01到MAX_LAT+0.01
     * @param lon 经度值，有效范围为MIN_LON-0.01到MAX_LON+0.01
     * @return 如果经纬度坐标在有效范围内返回true，否则返回false
     */
    private boolean isValidGeoCoordinate(double lat, double lon) {
        // 检查纬度和经度是否都在扩展的有效范围内
        return lat >= MIN_LAT - 0.01 && lat <= MAX_LAT + 0.01
                && lon >= MIN_LON - 0.01 && lon <= MAX_LON + 0.01;
    }


    /**
     * 从日期数组中提取指定索引的时间值
     *
     * @param dateArray 包含日期信息的数组对象
     * @param timeIndex 时间索引位置
     * @return 返回提取的时间值，对于二维数组返回年份*100+月份，对于一维数组返回数组中的double值
     * @throws IllegalArgumentException 当数组维度既不是1维也不是2维时抛出异常
     */
    private double extractTime(Array dateArray, int timeIndex) {
        int[] dims = dateArray.getShape();
        Index index = dateArray.getIndex();

        // 处理二维数组情况：提取年份和月份信息
        if (dims.length == 2) {
            int year = dateArray.getInt(index.set(0, timeIndex));
            int month = dateArray.getInt(index.set(1, timeIndex));
            return year * 100 + month;
        // 处理一维数组情况：直接返回指定索引的double值
        } else if (dims.length == 1) {
            return dateArray.getDouble(index.set(timeIndex));
        }
        throw new IllegalArgumentException("时间变量维度不符合要求：" + Arrays.toString(dims));
    }


    /**
     * 读取NetCDF变量中的二维数组数据
     *
     * @param var 要读取的NetCDF变量对象
     * @return 包含变量数据的二维double数组
     * @throws IOException 当读取变量数据时发生IO异常
     */
    private double[][] read2DArray(Variable var) throws IOException {
        Array array = var.read();
        int[] shape = array.getShape();
        // 验证数组维度，确保是二维数组
        if (shape.length != 2) {
            throw new IllegalArgumentException("经纬度变量应为二维数组：" + Arrays.toString(shape));
        }

        // 初始化结果数组并逐个读取数据
        double[][] result = new double[shape[0]][shape[1]];
        Index index = array.getIndex();
        for (int i = 0; i < shape[0]; i++) {
            for (int j = 0; j < shape[1]; j++) {
                result[i][j] = array.getDouble(index.set(i, j));
            }
        }
        return result;
    }


    /**
     * 获取变量属性的双精度浮点数值
     * @param var 变量对象
     * @param name 属性名称
     * @param defaultValue 默认值，当属性不存在时返回此值
     * @return 属性的双精度浮点数值，如果属性不存在则返回默认值
     */
    private double getAttributeDouble(Variable var, String name, double defaultValue) {
        ucar.nc2.Attribute attr = var.findAttribute(name);
        return attr != null ? attr.getNumericValue().doubleValue() : defaultValue;
    }

    /**
     * 获取变量属性的短整型数值
     * @param var 变量对象
     * @param name 属性名称
     * @param defaultValue 默认值，当属性不存在时返回此值
     * @return 属性的短整型数值，如果属性不存在则返回默认值
     */
    private short getAttributeShort(Variable var, String name, short defaultValue) {
        ucar.nc2.Attribute attr = var.findAttribute(name);
        return attr != null ? attr.getNumericValue().shortValue() : defaultValue;
    }

    /**
     * 根据给定值更新边界范围
     * @param value 当前值
     * @param min 最小值的原子引用
     * @param max 最大值的原子引用
     */
    private void updateBoundaryByMat(double value, AtomicReference<Double> min, AtomicReference<Double> max) {
        if (value < min.get()) min.set(value);
        if (value > max.get()) max.set(value);
    }


    /**
     * 构建包含海洋流数据和相关元信息的结果映射
     *
     * @param dataList 海洋流数据列表
     * @param minLon 经度最小值
     * @param maxLon 经度最大值
     * @param minLat 纬度最小值
     * @param maxLat 纬度最大值
     * @param minVal 数据值最小值
     * @param maxVal 数据值最大值
     * @param units 数据单位
     * @return 包含数据列表、边界范围和数值范围信息的映射
     */
    private Map<String, Object> buildResultMap(List<OceanCurrentData> dataList,
                                               double minLon, double maxLon,
                                               double minLat, double maxLat,
                                               double minVal, double maxVal,
                                               String units) {
        Map<String, Object> result = new HashMap<>();
        result.put("dataList", dataList);

        // 构建地理边界信息
        Map<String, Double> bounds = new HashMap<>();
        bounds.put("minLon", minLon);
        bounds.put("maxLon", maxLon);
        bounds.put("minLat", minLat);
        bounds.put("maxLat", maxLat);
        result.put("bounds", bounds);

        // 构建数值范围信息
        Map<String, Object> valueRange = new HashMap<>();
        valueRange.put("minValue", minVal);
        valueRange.put("maxValue", maxVal);
        valueRange.put("units", units);
        result.put("valueRange", valueRange);

        return result;
    }


    public static class OceanCurrentData {
        private BigDecimal lat;
        private BigDecimal lon;
        private BigDecimal value;
        private BigDecimal time;
        private BigDecimal depth;

        // Getter和Setter
        public BigDecimal getLat() { return lat; }
        public void setLat(BigDecimal lat) { this.lat = lat; }
        public BigDecimal getLon() { return lon; }
        public void setLon(BigDecimal lon) { this.lon = lon; }
        public BigDecimal getValue() { return value; }
        public void setValue(BigDecimal value) { this.value = value; }
        public BigDecimal getTime() { return time; }
        public void setTime(BigDecimal time) { this.time = time; }
        public BigDecimal getDepth() { return depth; }
        public void setDepth(BigDecimal depth) { this.depth = depth; }
    }








    /**
     * 读取指定经纬度、时间索引的海流数据，生成水深折线图所需的数据结构。
     *
     * 该方法适用于 NetCDF 文件中包含如下结构的变量：
     * - lat(288, 324)：纬度二维数组
     * - lon(288, 324)：经度二维数组
     * - depth(30, 324, 288)：水深三维数组
     * - date(6, 240)：时间二维数组
     * - data(240, 288, 324, 30)：海流数据四维数组（如 u/v 分量）
     *
     * Y轴表示水深（从深到浅排序），X轴表示对应深度的海流数值。
     *
     * @param filePath   NetCDF 文件路径
     * @param targetLon  目标经度（东经）
     * @param targetLat  目标纬度（北纬）
     * @param val        海流变量名称（如 "u" 或 "v"）
     * @param timeIndex  时间维度索引（范围：0~239）
     * @return 包含绘图所需数据的 Map，键值如下：
     *         - "depths": List<Double>，按深度从深到浅排列的水深值
     *         - "values": List<Double>，与 depths 对应的海流值（可能为 null 表示无效值）
     *         - "units": String，海流数据单位（默认 m/s）
     *         - "time": String，格式化的时间字符串（yyyy-MM-dd HH:mm:ss）
     *         - "depthsUnits": String，水深单位（默认 m）
     *         - "targetLonLat": String，目标经纬度描述
     *         - "matchedGrid": String，匹配到的网格点索引信息
     * @throws IOException 如果文件读取失败、变量缺失或索引越界
     */
    @Override
    public Map<String, Object> oceanCurrentDepthLineChart(String filePath, double targetLon, double targetLat, String val, int timeIndex) throws IOException {
        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 获取核心变量（精准匹配文档变量名）
            Variable latVar = ncFile.findVariable("lat");       // 纬度变量：lat(288,324)
            Variable lonVar = ncFile.findVariable("lon");       // 经度变量：lon(288,324)
            Variable depthVar = ncFile.findVariable("depth");   // 水深变量：depth(30,324,288)
            Variable dateVar = ncFile.findVariable("date");     // 时间变量：date(6,240)
            Variable dataVar = ncFile.findVariable(val);        // 海流数据变量：data(240,288,324,30)

            // 1.2 校验变量是否存在
            List<String> missingVars = new ArrayList<>();
            if (latVar == null) missingVars.add("lat");
            if (lonVar == null) missingVars.add("lon");
            if (depthVar == null) missingVars.add("depth");
            if (dateVar == null) missingVars.add("date");
            if (dataVar == null) missingVars.add(val);
            if (!missingVars.isEmpty()) {
                throw new IllegalArgumentException("缺失变量：" + String.join(", ", missingVars) + "（参考文档：需包含lat/lon/depth/date/data）");
            }

            // 2. 读取经纬度数组并使用二分查找定位索引（核心优化）
            double[][] lonArray = read2DArray(lonVar); // [288][324]：经度数组
            double[][] latArray = read2DArray(latVar); // [288][324]：纬度数组

            // 2.1 提取参考行列的一维数组用于二分查找
            int refRow = lonArray.length / 2; // 中间行作为参考（144）
            double[] refLonLine = extractRow(lonArray, refRow); // 参考行的经度数组
            double[] refLatLine = extractColumn(latArray, 0);   // 第一列的纬度数组

            // 2.2 使用二分查找快速定位近似索引（复用已有方法）
            int approxLonIdx = findNearestIndex(refLonLine, targetLon);
            int approxLatIdx = findNearestIndex(refLatLine, targetLat);

            // 2.3 在小范围内精确匹配（解决二维网格非线性问题）
            int[] lonLatIndex = findExactIndexInNeighborhood(
                    lonArray, latArray, targetLon, targetLat,
                    approxLonIdx, approxLatIdx, 5); // 搜索半径5个网格
            int targetLonIdx = lonLatIndex[0];
            int targetLatIdx = lonLatIndex[1];

            System.out.printf("目标经纬度（%.2f°E, %.2f°N）匹配到网格点：lonIdx=%d, latIdx=%d%n",
                    targetLon, targetLat, targetLonIdx, targetLatIdx);

            // 2.4 读取水深数组和时间数组
            Array depthArray = depthVar.read(); // 水深完整数组
            Array dateArray = dateVar.read();   // 时间完整数组

            // 3. 校验时间索引有效性
            int maxTimeIndex = dateVar.getDimensions().get(1).getLength() - 1; // 0-239
            if (timeIndex < 0 || timeIndex > maxTimeIndex) {
                throw new IndexOutOfBoundsException("时间索引超出范围：" + timeIndex + "（有效范围：0~" + maxTimeIndex + "）");
            }

            // 4. 解析时间（格式：yyyy-MM-dd HH:mm:ss）
            String formattedTime = parseDateToFormattedString(dateArray, timeIndex);

            // 5. 确定海流数据的维度顺序（data(240,288,324,30)）
            int timeDimIndex = 0;    // 时间维度索引
            int lonDimIndex = 1;     // 经度维度索引
            int latDimIndex = 2;     // 纬度维度索引
            int depthDimIndex = 3;   // 水深维度索引
            int[] dataShape = dataVar.getShape();
            int depthLevelCount = dataShape[depthDimIndex]; // 水深层数：30

            // 6. 处理数据类型转换（支持short压缩）
            boolean isShortType = dataVar.getDataType().equals(DataType.SHORT);
            double scaleFactor = isShortType ? getAttributeDouble(dataVar, "scale_factor", 1.0) : 1.0;
            double addOffset = isShortType ? getAttributeDouble(dataVar, "add_offset", 0.0) : 0.0;
            short fillValue = isShortType ? getAttributeShort(dataVar, "_FillValue", (short) -9999) : 0;
            short missingValue = isShortType ? getAttributeShort(dataVar, "missing_value", (short) -9999) : 0;

            // 7. 读取所有水深层的海流数据和对应水深值
            List<Double> depths = new ArrayList<>(depthLevelCount);   // Y轴：水深值
            List<Double> currentValues = new ArrayList<>(depthLevelCount); // X轴：海流值

            for (int depthIdx = 0; depthIdx < depthLevelCount; depthIdx++) {
                // 7.1 读取当前水深层的水深值（depth(30,324,288)）

                Index depthIndex = depthArray.getIndex().set(depthIdx, targetLatIdx, targetLonIdx);
                double currentDepth = depthArray.getDouble(depthIndex);
                // 核心修改：保留小数点后两位（四舍五入）
                currentDepth = Math.round(currentDepth * 100.0) / 100.0;


                // 7.2 读取当前水深层的海流数据（data(240,288,324,30)）
                int[] origin = new int[dataShape.length];
                origin[timeDimIndex] = timeIndex;
                origin[lonDimIndex] = targetLonIdx;
                origin[latDimIndex] = targetLatIdx;
                origin[depthDimIndex] = depthIdx;
                int[] shape = new int[dataShape.length];
                Arrays.fill(shape, 1);
                Array dataArray = dataVar.read(origin, shape);

                // 7.3 处理海流值（类型转换和无效值过滤）
                double currentVal;
                if (isShortType) {
                    short rawVal = dataArray.getShort(0);
                    if (rawVal == fillValue || rawVal == missingValue) {
                        currentVal = Double.NaN;
                    } else {
                        currentVal = rawVal * scaleFactor + addOffset;
                    }
                } else {
                    currentVal = dataArray.getDouble(0);
                }

                // 7.4 存储数据
                depths.add(currentDepth);
                currentValues.add(Double.isNaN(currentVal) ? null : formatDecimalValue(currentVal));
            }

            // 8. 水深排序（从深到浅，符合观看习惯）
            List<Double> sortedDepths = new ArrayList<>(depths);
            List<Double> sortedCurrentValues = new ArrayList<>(currentValues);
            Collections.reverse(sortedDepths);
            Collections.reverse(sortedCurrentValues);

            // 9. 封装结果
            Map<String, Object> result = new HashMap<>();
            result.put("depths", sortedDepths);          // 水深值（从深到浅）
            result.put("values", sortedCurrentValues);      // 海流数据值
            result.put("units", getAttributeValue(dataVar, "units", "m/s")); // 海流单位
            result.put("time", formattedTime);              // 格式化时间
            result.put("depthsUnits", getAttributeValue(depthVar, "units", "m")); // 水深单位
            result.put("targetLonLat", String.format("%.2f°E, %.2f°N", targetLon, targetLat));
            result.put("matchedGrid", String.format("lonIdx=%d, latIdx=%d", targetLonIdx, targetLatIdx));

            return result;

        } catch (InvalidRangeException | ParseException e) {
            throw new IOException("读取海流水深折线图数据失败，文件路径：" + filePath, e);
        }
    }


    // -------------------------- 核心优化：二分查找相关方法 --------------------------


    /**
     * 在二分查找得到的近似索引周围小范围搜索，找到最匹配的经纬度索引
     *
     * @param lonArray 经度数组，二维数组形式存储经纬度网格数据
     * @param latArray 纬度数组，二维数组形式存储经纬度网格数据
     * @param targetLon 目标经度值
     * @param targetLat 目标纬度值
     * @param approxLonIdx 近似经度索引，通常由二分查找获得
     * @param approxLatIdx 近似纬度索引，通常由二分查找获得
     * @param searchRadius 搜索半径，确定在近似索引周围搜索的范围
     * @return 包含最优匹配经纬度索引的数组，[0]为经度索引，[1]为纬度索引
     */
    private int[] findExactIndexInNeighborhood(double[][] lonArray, double[][] latArray,
                                               double targetLon, double targetLat,
                                               int approxLonIdx, int approxLatIdx,
                                               int searchRadius) {
        int rows = lonArray.length;
        int cols = lonArray[0].length;

        // 计算搜索范围（避免数组越界）
        int startLon = Math.max(0, approxLonIdx - searchRadius);
        int endLon = Math.min(rows - 1, approxLonIdx + searchRadius);
        int startLat = Math.max(0, approxLatIdx - searchRadius);
        int endLat = Math.min(cols - 1, approxLatIdx + searchRadius);

        // 查找最小距离的网格点
        double minDistance = Double.MAX_VALUE;
        int bestLonIdx = approxLonIdx;
        int bestLatIdx = approxLatIdx;

        for (int lonIdx = startLon; lonIdx <= endLon; lonIdx++) {
            for (int latIdx = startLat; latIdx <= endLat; latIdx++) {
                // 计算经纬度与目标点的距离（球面距离）
                double distance = calculateGeoDistance(
                        latArray[lonIdx][latIdx], lonArray[lonIdx][latIdx],
                        targetLat, targetLon);

                if (distance < minDistance) {
                    minDistance = distance;
                    bestLonIdx = lonIdx;
                    bestLatIdx = latIdx;
                }
            }
        }

        return new int[]{bestLonIdx, bestLatIdx};
    }



    /**
     * 计算两点之间的球面距离（单位：千米）
     * 使用Haversine公式计算地球表面上两点之间的最短距离。
     *
     * @param lat1 第一个点的纬度（十进制度数）
     * @param lon1 第一个点的经度（十进制度数）
     * @param lat2 第二个点的纬度（十进制度数）
     * @param lon2 第二个点的经度（十进制度数）
     * @return 返回两点之间的球面距离，单位为千米
     */
    private double calculateGeoDistance(double lat1, double lon1, double lat2, double lon2) {
        final double R = 6371; // 地球半径（千米）
        double latDiff = Math.toRadians(lat2 - lat1);
        double lonDiff = Math.toRadians(lon2 - lon1);

        double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return R * c;
    }

    /**
     * 从二维数组中提取指定行的数据副本
     *
     * @param array    源二维数组
     * @param rowIndex 要提取的行索引
     * @return 返回指定行数据的副本
     * @throws IndexOutOfBoundsException 当行索引超出有效范围时抛出异常
     */
    private double[] extractRow(double[][] array, int rowIndex) {
        if (rowIndex < 0 || rowIndex >= array.length) {
            throw new IndexOutOfBoundsException("行索引超出范围: " + rowIndex);
        }
        return array[rowIndex].clone();
    }

    /**
     * 从二维数组中提取指定列的数据
     *
     * @param array    源二维数组
     * @param colIndex 要提取的列索引
     * @return 返回包含指定列所有元素的一维数组
     * @throws IndexOutOfBoundsException 当列索引超出有效范围或数组为空时抛出异常
     */
    private double[] extractColumn(double[][] array, int colIndex) {
        if (array.length == 0 || colIndex < 0 || colIndex >= array[0].length) {
            throw new IndexOutOfBoundsException("列索引超出范围: " + colIndex);
        }
        double[] column = new double[array.length];
        for (int i = 0; i < array.length; i++) {
            column[i] = array[i][colIndex];
        }
        return column;
    }

    /**
     * 解析date(6,240)数组中的时间数据并格式化为字符串
     * 该方法读取指定时间点的年、月、日、时、分、秒字段，并转换为"yyyy-MM-dd HH:mm:ss"格式的时间字符串。
     *
     * @param dateArray  包含日期时间信息的多维数组，维度为(6, 240)，每列表示一个时间点
     * @param timeIndex  要解析的时间点索引（对应数组的第二维）
     * @return 返回格式化后的时间字符串，格式为"yyyy-MM-dd HH:mm:ss"
     * @throws ParseException 如果在解析过程中发生错误则抛出此异常
     */
    private String parseDateToFormattedString(Array dateArray, int timeIndex) throws ParseException {
        Index index = dateArray.getIndex();
        // 读取当前时间点的6个字段：年(0)、月(1)、日(2)、时(3)、分(4)、秒(5)
        int year = (int) dateArray.getDouble(index.set(0, timeIndex));
        int month = (int) dateArray.getDouble(index.set(1, timeIndex));
        int day = (int) dateArray.getDouble(index.set(2, timeIndex));
        int hour = (int) dateArray.getDouble(index.set(3, timeIndex));
        int minute = (int) dateArray.getDouble(index.set(4, timeIndex));
        int second = (int) dateArray.getDouble(index.set(5, timeIndex));

        // 构建Calendar对象（处理月份从0开始的问题）
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, day, hour, minute, second);
        calendar.set(Calendar.MILLISECOND, 0);

        // 格式化时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(calendar.getTime());
    }




    @Override
    @Anonymous
    public List<Map<String, String>> processReadAllParametersByOceanCurrent(@RequestParam("filePath") String filePath) {
        List<Map<String, String>> variableList = new ArrayList<>();
        int id = 1; // 递增ID计数器

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            List<Variable> variables = ncFile.getVariables();

            for (Variable var : variables) {
                Map<String, String> variableInfo = new HashMap<>();
                String name = var.getShortName();
                variableInfo.put("id", String.valueOf(id++));
                variableInfo.put("variable", var.getFullName());
                variableInfo.put("label", getDictReplacedLabel(name));

                // 获取变量的维度信息
                List<ucar.nc2.Dimension> dimensions = var.getDimensions();
                // 过滤掉可能的无效维度，这里假设字符型变量不应有有效维度
                if (var.getDataType().isString()) {
                    dimensions = new ArrayList<>();
                }
                // 新增：若为结构体类型，将维度列表置空
                if (var instanceof ucar.nc2.Structure) {
                    dimensions = new ArrayList<>();
                }
                int dimCount = dimensions.size();
                StringBuilder dimInfo = new StringBuilder();
                for (int i = 0; i < dimensions.size(); i++) {
                    if (i > 0) {
                        dimInfo.append(", ");
                    }
                    ucar.nc2.Dimension dim = dimensions.get(i);
                    dimInfo.append(dim.getShortName());
                }

                // 判断变量类型，没有维度时 type 设为 "-"
                String varType;
                if (dimCount == 0 || dimensions.stream().anyMatch(d -> d.getLength() == 1)) {
                    varType = "-";
                } else {
                    // 仅当变量同时包含 "2*nscans" 和 "1KM_geo_dim" 维度时才判定为 Geo2D 类型
                    boolean has2NScans = dimensions.stream().anyMatch(d -> d.getShortName() != null &&
                            (d.getShortName().equals("2*nscans") || d.getShortName().equals("lat")));
                    boolean has1KMGeoDim = dimensions.stream().anyMatch(d -> d.getShortName() != null &&
                            (d.getShortName().equals("1KM_geo_dim") || d.getShortName().equals("lat")));
                    if (has2NScans && has1KMGeoDim) {
                        varType = "Geo2D";
                    } else {
                        // 假设只有两个独立有效维度时判定为2D
                        if (dimCount >= 2) {
                            varType = "2D";
                        } else {
                            varType = dimCount + "D"; // 这里会处理1D的情况
                        }
                    }
                }

                // 修改：只添加type不为"-"的变量
                if (!"-".equals(varType)) {
                    variableInfo.put("type", varType);
                    if (!dimensions.isEmpty()) {
                        variableInfo.put("dimensions", dimInfo.toString());
                    }
                    variableList.add(variableInfo);
                }
            }

        } catch (IOException e) {
            System.err.println("读取变量列表时出错: " + e.getMessage());
            e.printStackTrace();
            // 出错时返回空列表
        }

        return variableList;
    }





    /**
     * 读取 NetCDF 文件中的一维线图数据，包括数组下标轴和对应的数据值，并返回封装后的结果。
     *
     * @param filePath NetCDF 文件的路径
     * @param val      需要读取的数据变量名称
     * @return 返回包含读取结果的 Map，结构如下：
     *         - error（可选）: String，错误信息（仅在失败时存在）
     *         - xAxis（可选）: Map，包含X轴（数组下标）信息（仅在成功时存在）
     *             - data: List<Integer>，过滤后的下标列表
     *             - name: String，X轴名称
     *         - data（可选）: Map，包含数据信息（仅在成功时存在）
     *             - values: List<Double>，实际数据值
     *             - name: String，数据变量名称
     *             - units: String，数据单位
     *             - minValue: Number，数据最小值
     *             - maxValue: Number，数据最大值
     *             - validCount: Integer，有效数据条数
     */
    @Override
    public Map<String, Object> readOceanCurrentTimeSeries(String filePath, String val) {
        Map<String, Object> result = new LinkedHashMap<>();

        try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
            // 1. 安全获取目标数据变量
            Variable dataVar = findVariableSafely(ncFile, val, "一维数据变量");

            // 2. 校验变量是否为一维
            int[] dataShape = dataVar.getShape();
            if (dataShape.length != 1) {
                result.put("error", "目标变量必须为一维结构，实际维度：" + Arrays.toString(dataShape) + "（变量名：" + val + "）");
                return result;
            }

            // 3. 读取变量的原始数据
            Array dataArray = dataVar.read();
            Index dataIndex = dataArray.getIndex();
            int totalCount = dataShape[0];

            // 4. 过滤NaN数据：同步收集“有效数据值”和“对应下标”
            List<Double> validDataValues = new ArrayList<>();
            List<Integer> validIndexes = new ArrayList<>();

            for (int i = 0; i < totalCount; i++) {
                dataIndex.set(i);
                double value;

                // 兼容不同数值类型
                if (dataArray.getElementType() == double.class) {
                    value = dataArray.getDouble(dataIndex);
                } else if (dataArray.getElementType() == float.class) {
                    value = dataArray.getFloat(dataIndex);
                } else if (dataArray.getElementType() == int.class) {
                    value = dataArray.getInt(dataIndex);
                } else {
                    result.put("error", "不支持的数值类型：" + dataArray.getElementType().getSimpleName() + "（变量名：" + val + "）");
                    return result;
                }

                // 过滤NaN：仅保留有效数值，并记录对应下标
                if (!Double.isNaN(value)) {
                    validDataValues.add(value);
                    validIndexes.add(i);
                }
            }

            // 5. 处理无有效数据的场景
            if (validDataValues.isEmpty()) {
                result.put("error", "目标变量所有数据均为NaN，无有效数据可展示（变量名：" + val + "）");
                return result;
            }

            // 6. 计算过滤后数据的最值
            double minValue = Collections.min(validDataValues);
            double maxValue = Collections.max(validDataValues);

            // 7. 获取数据变量的元信息
            String dataName = getAttributeValue(dataVar, "long_name", val);
            String dataUnits = getAttributeValue(dataVar, "units", "无单位");

            // 8. 封装X轴（数组下标）信息
            Map<String, Object> xAxisMap = new HashMap<>();
            xAxisMap.put("data", validIndexes);
            xAxisMap.put("name", "数组下标");
            result.put("xAxis", xAxisMap);

            // 9. 封装数据信息
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("values", validDataValues);
            dataMap.put("name", dataName);
            dataMap.put("units", dataUnits);
            dataMap.put("minValue", minValue);
            dataMap.put("maxValue", maxValue);
            dataMap.put("validCount", validDataValues.size());
            result.put("data", dataMap);

            return result;

        } catch (IOException e) {
            result.put("error", "NC文件读取失败：" + e.getMessage() + "（文件路径：" + filePath + "）");
            return result;
        } catch (IllegalArgumentException e) {
            result.put("error", e.getMessage());
            return result;
        }
    }


}